Enfin un moyen d’afficher une vidéo avec des cases à cocher (PyQt4 et OpenCV inside)


Ceci est un post invité de 01ivier posté sous licence creative common 3.0 unported.

Bonsjours les amis,

J’espère que vous allez bien et que vous n’avez pas trop pensé que j’étais mort rapport au fait que ça faisait longtemps que je n’avais pas posté d’article.

 

Où je mentionne plusieurs fois le mot JavaScript.

Le fait est que j’étais trop occupé à faire du JavaScript.

Bon, ce n’est pas exactement vrai, mais, je voulais vérifier une théorie selon laquelle dès qu’on parle de JavaScript sur Sam&Max, le nombre des commentaires explosent (je n’ai pas de théorie sur le fait qu’il faille l’écrire en gras, mais je trouve ça joli (sauf quand il y en a partout (parce qu’après ça fait trop))).

Mais, dire que j’étais trop occupé à faire du JavaScript n’est pas précisément faux non plus dans la mesure où je me suis mis à p5.js, qui est au JavaScript ce que Processing est au Java, à savoir un environnement de développement simplifié pour les pucelles et autres puceaux de la programmation.

p5.js qui m’a permis d’assouvir un de mes fantasmes : afficher un cheval qui court avec des cases à cocher dans une page web..

Un cheval qui court en cases à cocher d'après les célèbres photographies d'Easweard Muybridge, désormais dans le domaine publique.

Un cheval qui court en cases à cocher d’après les photos d’E.Muybridge, désormais dans le domaine publique.

Je vous ai mis un GIF animé, pour faire de l’animation, mais vous pouvez le voir en vrai ici avec vos cases à cocher à vous.

(Sauf pour celles et ceux pour qui ça ne marchera pas, bien évidemment.)

(Voilà pour la partie JavaScript. Je ne vais plus en parler. Les personnes concernées peuvent donc se rendre dès à présent à la section des commentaires. Merci.)

 

Où je m’attelle à une tâche bien plus sérieuse.

J’étais content, mais, passé ces enfantillages, il me fallait désormais m’atteler à une tâche bien plus sérieuse : afficher un cheval qui court avec des cases à cocher en Python.

Pour cela, j’ai choisi d’utiliser PyQt4 parce que c’est la meilleure librairie pour créer des interfaces graphiques en Python.

Ah Ah Ah…
Nan, mais, comme si j’en savais quelque chose…
C’est juste qu’un ami avait déjà réussi à afficher un bouton avec…
Donc, je me suis dit que c’était pour moi…
Bon, en fait, ce n’est pas pour moi…
Mais, que les choses soient claires, c’est l’informatique en général et la programmation en particulier qui ne sont pas pour moi…
Je veux dire, quand je vais lire Stack Overflow, j’ai de la peine pour moi…
C’est à dire que je me fais vraiment pitié tant je comprends que dalle…
Il m’est déjà arrivé de mettre une dizaine de secondes pour savoir si je lisais bien du Python (et s’en était)…
Nan, c’est moche…
Mais bon, je ne sais faire que de l’informatique (on me souffle à l’oreille que je sais aussi faire du Chirashi et c’est vrai)
Donc, je fais de l’informatique… (et du Chirashi)
Et j’en chie…
Mais à la fin je gagne…
Et je me paye même le luxe d’en faire un article sur S&M…
Pourquoi je vous raconte ça ?
Aucune idée…
Toujours est-il que j’ai choisi PyQt4
Que ça n’est pas pour moi…
Mais que [SPOILER ALERT] ça a fini par marcher…

 

Où j’affiche une fenêtre. UNE FE-NÊ-TRE. Oui messieurs, dames.

D’abord, il faut installer PyQt4

sudo apt-get install python-qt4

…et on ne peut pas dire que ce soit trop compliqué.

Et, voici le code minimal pour afficher une fenêtre :

#-*- coding: utf-8 -*-
from PyQt4 import QtGui
 
# On crée une appli Qt
application = QtGui.QApplication([])
# On fout quelque chose dedans
fond = QtGui.QWidget()
# On l'affiche
fond.show()
# On lance l'appli
application.exec_()

Les connaisseurs apprécieront mon désormais célèbre style procédural de champion du monde.
Mais bon. Ça marche où ça marche pas ?

Une fenêtre

Une fenêtre

Ça marche.

 

Où j’affiche un bouton dans la fenêtre.

Toujours en procédural, voici comment on ajoute un bouton à notre fenêtre (que l’on customize un peu au passage).
(J’insiste pour le procédural car, comme PyQt4 est conçu pour être utilisé en Programmation Orientée Objet, vous ne verrez pas ça tous les jours)

#-*- coding: utf-8 -*-
from PyQt4 import QtGui
 
app = QtGui.QApplication([])
 
# On crée un fond et lui donne une taille, une position sur l'écran et un titre
fond = QtGui.QWidget()  
fond.resize(300, 200)
fond.move(100, 100) 
fond.setWindowTitle("C'est formidable") 
 
# On crée un bouton sur notre fond et on lui donne une position sur celui-ci
bouton = QtGui.QPushButton('Mon petit bouton',fond)
bouton.move(80, 80)
 
fond.show()
app.exec_()

Et hop…

Un petit bouton dans une fenêtre

Un petit bouton dans une fenêtre

La première fois que ça a marché, j’ai cliqué 25 fois sur le bouton et j’en garde un très bon souvenir.

 

Où j’affiche plein de cases à cocher dans la fenêtre.

Bon, on peut afficher tout un tas de bordel dans une fenêtre, vous vous en doutez. Des images, du texte, des champs texte, des sliders, des menus déroulants, des labels, des séparations…
Mais, ça n’est pas le propos ici.
Nous ce qu’on veut, ce sont des cases à cocher. Plein de cases à cocher.

J’ai commencé par les placer dans une grille, mais, je me suis rendu compte qu’en leur donnant des positions absolues en pixels, il était possible de les faire se toucher, voire se chevaucher. Ce qui, quand on veut afficher un cheval, apporte un petit plus indéniable.
(chevaucher/cheval/humour)

Allons y donc pour nos cases à cocher.
(Je vous recolle tout à chaque fois parce que je sais qu’il y a des goulus parmi vous.)

#-*- coding: utf-8 -*-
from PyQt4 import QtGui
 
# On définie les dimensions de notre bazar
largeur = 40
hauteur = 30
tailleCase =14
 
app = QtGui.QApplication([])
 
fond = QtGui.QWidget()  
fond.resize(largeur*tailleCase+6, hauteur*tailleCase+6) # le +6 c'est pour tenir compte des marges
fond.setWindowTitle(u"Plein de cases à cocher") 
 
# On parcourt les ordonnées
for j in range(hauteur):
 
    # On parcourt les abscisses
    for i in range(largeur):
 
        # On crée une case à cocher sur notre fond
        check_box = QtGui.QCheckBox(fond)
        # Et on la positionne
        check_box.move(i*tailleCase, j*tailleCase)
 
fond.show()
app.exec_()
Le mot

Le mot “Joie” écrit à la main avec des cases à cocher.

 

Où j’affiche plein de cases à cocher dans la fenêtre mais en utilisant cette fois la Programmation Orientée Objet.

On arrive au bout. Mais, ce qu’il faut comprendre avec pyQt c’est que quand le programme arrive à l’instruction app.exec_() il démarre une boucle. On est alors un peu coincé pour parler avec l’interface.
Il est possible de cliquer sur les cases à la main, soit, mais, nous ce qu’on veut, c’est que ce soit un bout de code qui le fasse à notre place (car ayant bien mis 2 minutes pour composer une seule image affichant le mot “Joie”, j’ai des doutes pour le 25 fps).

Dans tous les cas, il n’est plus possible de rester en procédural. Voici donc la version POO du précédent script :

#-*- coding: utf-8 -*-
from PyQt4 import QtGui
import sys
 
largeur = 40
hauteur = 30
tailleCase = 14
 
class CheckBoxVideo(QtGui.QWidget):
 
    # On initialise la class, j'imagine...
    def __init__(self):
        super(CheckBoxVideo, self).__init__()
        self.interface()
 
    # On initialise l'interface    
    def interface(self):
 
        self.resize(largeur*tailleCase+6, hauteur*tailleCase+6)
        self.setWindowTitle(u"Plein de cases à cocher, mais en POO.")
 
        for j in range(hauteur):
 
            for i in range(largeur):
 
                check_box = QtGui.QCheckBox(self)
                check_box.move(i*tailleCase, j*tailleCase)     
 
        self.show()
 
 
if __name__ == "__main__":
      appli = QtGui.QApplication(sys.argv)
      ckbx = CheckBoxVideo()
      sys.exit(appli.exec_())
Le mot "Bonheur" écrit en cases à cocher mais en utilisant cette fois -ci la programmation orientée objet.

Le mot “Bonheur” écrit en cases à cocher mais en utilisant cette fois la programmation orientée objet.

J’ai ajouté un petit if __name__ == "__main__" histoire de laisser croire que je comprends ce que je fais.
Et puis j’ai intégré la librairie sys pour être conforme avec tous les exemples de tous les tutos que j’ai trouvé. Mais c’est vraiment par respect pour les devs qui se sont donné du mal à les écrire. Parce que, vous pouvez essayer, ça marche très bien sans.
Bref.

 

Où, en faisant ce qu’il ne faut pas faire, je constate qu’il ne faut pas le faire.

Je vous épargnerai la liste des trucs ridicules que j’ai essayé pour entrer dans cette boucle et modifier l’état des cases à cocher et n’évoquerai que les solutions que j’ai trouvé pour faire marcher mon bazar… pendant 20 secondes.

En effet, à force de chercher, je suis tombé sur ce fil de Stack Overflow où la réponse la mieux notée donne trois moyens différents de créer des threads avec Qt.

J’ai méticuleusement tout recopié à l’arrache et testé chacune des solutions.
Toutes m’ont permis de voir ma silhouette en cases à cocher avant de me retourner un délicat segfault au bout de 20 secondes.

Vraisemblablement, il y a quelque chose que je faisais mal.
Chose qui a été confirmée par un article intitulé : “You’re doing it wrong…”

C’est à ce moment que j’ai décidé de me coucher, vu que le Soleil se levait.

 

Où j’ai la confirmation que dormir peut s’avérer parfois utile pour se reposer.

Si j’étais mort dans mon sommeil ce jour là, mes derniers mots auraient été : “Putain de boucle de merde”.
Mais je me suis réveillé et je me suis dis que, tout de même, il devait y avoir un moyen simple de faire exécuter du code dans cette boucle principale.

Et là, avec un cerveau frais, je me suis souvenu de cet exemple expliquant comment animer une barre de progression.
Passer des valeurs à un objet pour en changer son état, c’est un peu ce que je cherchais à faire, non ?
Non.
C’était exactement ce que je cherchais à faire.

Le principe est de créer un timer et d’écouter ses tours d’horloge pour déclencher des événements.
À noter la nécessité d’importer QtCore pour cela.

#-*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
import sys
 
largeurOut = 40
hauteurOut = 30
tailleCase = 14
 
 
class CheckBoxVideo(QtGui.QWidget):
 
    def __init__(self):
        super(CheckBoxVideo, self).__init__()
        self.interface()
 
    def interface(self):
 
        self.resize(largeurOut*tailleCase+6, hauteurOut*tailleCase+6)
        self.setWindowTitle("OMG ! Mais que se passe-t-il dans la console ?")
 
        for j in range(hauteurOut):
 
            for i in range(largeurOut):
 
                check_box = QtGui.QCheckBox(self)
                check_box.move(i*tailleCase, j*tailleCase)
 
        # On crée le timer.
        self.timer = QtCore.QBasicTimer()
        # Et on le lance en renseignant un délais pour chaque tour d'horloge.
        # Ici, 40 millisecondes, pour tenter (naïvement) d'obtenir du 25 images par seconde.
        self.timer.start(40, self)
 
        self.show()
 
    # Et voici la méthode qui écoute le timer.
    def timerEvent(self, e):
 
        print("Ce message va s'écrire en console toutes les 40 ms.")
 
 
if __name__ == "__main__":
      appli = QtGui.QApplication(sys.argv)
      ckbx = CheckBoxVideo()
      sys.exit(appli.exec_())
Des cases à cocher dans une fenêtre et des messages dans une console.

Des cases à cocher dans une fenêtre et des messages dans une console.

 

Où j’évoque ma jeunesse.

Il s’agissait maintenant de trouver un moyen de traiter du flux vidéo.
Mais comme je suis un ouf et que non seulement j’avais déjà réussi à afficher de la vidéo 3-bit dans mon terminal , mais qu’en plus je vous en avais fait part ici-même, et bien je suis allé copier/coller mon pâté. Tout simplement.

Pour celles et ceux qui n’ont pas eu la force psychologique de cliquer sur le lien, sachez que la solution que j’avais retenue à l’époque pour lire un fichier vidéo afin d’en traiter les données était OpenCV, et que c’est tout aussi pertinent maintenant.
(Enfin, ça marche. Ce qui, à mon niveau, est la définition même de la pertinence.)

 

Où il est de nouveau affiché un cheval qui court en cases à cocher, mais en Python cette fois.

Et voilà…
C’est terminé…

Voici le script après l’intégration du code exploitant OpenCV :

#-*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
import sys
import cv2
 
largeur = 50
hauteur = 34
tailleCase = 14
 
# On crée une liste qui va stocker les QCheckBox
listeCB = []
 
# On définit un seuil pour pour cocher ou non les cases
seuil = 150
 
# On définit le fichier à lire.
video = cv2.VideoCapture('cheval_qui_court.mjpg')
 
class CheckBoxVideo(QtGui.QWidget):
 
    def __init__(self):
        super(CheckBoxVideo, self).__init__()
        self.interface()
 
    def interface(self):
 
        self.resize(largeur*tailleCase+6, hauteur*tailleCase+6)
        self.setWindowTitle(u"Un cheval qui court en cases à cocher")
 
        for j in range(hauteur): 
            for i in range(largeur): 
                check_box = QtGui.QCheckBox(self)
                check_box.move(i*tailleCase, j*tailleCase)
                # On ajoute chaque QCheckBox dans notre liste
                listeCB.append(check_box)   
 
        self.timer = QtCore.QBasicTimer()
        self.timer.start(40, self)
        self.show()
 
    def timerEvent(self, e):
 
        # On lit une frame de la vidéo
        ret,img = video.read()
        # On la passe en niveau de gris
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # On la retaille pour que le nombre de pixel corresponde au nombre de cases à cocher
        gray = cv2.resize(gray, (largeur, hauteur)) 
 
        # On parcourt notre liste
        for index, checkBox in enumerate(listeCB):
 
            # On transforme les index en coordonnées            
            x = index%largeur
            y = index/largeur
            # On récupère le niveau de gris du pixel concerné
            valeur = gray.item(y, x)
 
            # En fonction de la valeur on coche ou non la case à cocher
            if (valeur > seuil):
              checkBox.setCheckState(0)
            else:
              checkBox.setCheckState(2)
 
            # Les plus attentifs auront remarqué que l'état de la case à cocher ne dépend pas d'un booléen.
            # En effet, il existe un état intermédiaire, le (1), que j'ai appelé l'état de Schrödinger, où 
            # la case à cocher est partiellement cochée. Un peu comme si elle était à la fois cochée et non cochée.
            # (Et j'interdis à quiconque d'évoquer, qu'en fait, elle n'est ni l'une, ni l'autre.)
 
 
if __name__ == "__main__":
      appli = QtGui.QApplication(sys.argv)
      ckbx = CheckBoxVideo()
      sys.exit(appli.exec_())

Et voici le cheval qui court en cases à cocher :

Je concède qu’arrivé là, il n’aurait pas été totalement farfelu d’ajouter un slider pour gérer le niveau du seuil.
Mais j’avais un peu peur de rendre le truc utile, vous voyez.
Donc j’ai laissé en l’état.

 

Où je conclue.

Je pense qu’il est assez clair désormais qu’il est possible de sauver l’Humanité en programmant en Python.
Je vous encourage donc à le faire.

À bientôt ou tard.

 

Où j’ajoute tout de même un bonus.

Et voici un cheval qui court avec des cases à cocher qui se chevauchent.
Ne me remerciez pas, ça me fait plaisir.

cheval

20 thoughts on “Enfin un moyen d’afficher une vidéo avec des cases à cocher (PyQt4 et OpenCV inside)

  • boblinux

    J’ai lu le mot clef “javascript” je me suis rué ici, pour commenter, finalement ça en a valu le détour je me suis bien marré =P (prochaine étape faire ça avec tkinter? :o)

  • Fred

    Super beau. Je ne connaissois point opencv mais j’aime bien.

    Allez, petite optimisaiton possible: (y, x) = divmod(index, largeur) et surtout checkBox.setChecked(valeur > seuil). Ben oui, étant donné que la case à cocher n’est pas triState (ou SchrödingerState) on a alors le droit de raccourcir et passer par setChecked() au lieu de setCheckState().

    Ceci dit, si on veut vraiment passer par setCheckState(), alors le puriste préfèrera utiliser Qt.UnChecked et Qt.Checked au lieu de ces nombres magiques 0 et 2.

    Bon allez, je vais convertir le film “Autant en emporte le vent” en cases à cocher…;)

  • B.Pivot

    C’est bien mais moi je voulais un poney !

    ptite typo : fil-s de Stack Overflow, au singulier, sinon ça fait une insulte chelou

  • 01ivier Post author

    @dineptus : voilà un commentaire simple et efficace. Merci. :-)

    @boblinux : ça se tente… :-p

    @Fred : (y, x) = divmod(index, largeur)… whaou, merci… j’ai l’impression d’avoir un troisième bras… :-D

    Et, effectivement, setChecked est exactement adapté à la situation et c’est ce que je fais dans la version JS, je suis juste tombé en premier sur setCheckState et je n’ai pas cherché plus loin… (selon la fameuse règle : si ça marche, c’est que c’est pertinent :-) )

    Merci donc pour ces remarques.

    Je vais modifier le post en conséquence d’ici quelques jours dans la mesure où cela simplifie à mon sens la lecture du script.

    @B.Pivot : Ah Ah… merci… fixed…

  • deuzair

    Merci pour ce super tuto, je commence à coder avec pyqt depuis quelques mois et j’adore !!

    Mais pourquoi pyqt4 et pas pyqt5? Une raison particulière?

  • buffalo974

    Super !

    ça m’intéresse beaucoup ce que tu as fait, mais allons plus loin :

    Plutôt que d’obtenir des cases à cocher, comment faire pour obtenir un gif animé étant traité au préalable

    par un logiciel comme GMIC :

    On pourrait faire une espèce de dessin animé dégradé à partir de videos !

    Très intéressant pour des scènes de jeux vidéos.

    Je pense qu’il faille utiliser pyOpenCL.

    Si ce n’est pas faisable via GMIC, peut etre avec le module pillow, ou par un script pour GIMP…

  • dway

    Pardon, mais tout ça va encore troubler les jeunes de France.

    Comment expliquer que le cocher peut constituer lui-même le cheval alors qu’il est communément admis qu’il est sur le cheval (ou derrière, sur un siège en cuir sale).

    Hein ? HEIN ?

  • 01ivier Post author

    @Louis : J’essaie de coller au mieux au concept d’Innovation promu par la #FrenchTech. À savoir exploiter des idées vieilles de 20 ans pour en faire quelque chose d’inutile. Je trouve que je m’en sors pas mal… :-p

    @deuzair : C’est juste que PyQt5 n’est pas dans les dépôts par défaut sur Ubuntu 14.04 (en tout cas pour Python 2.7).

    @buffalo974 : Un filtre vidéo, en somme. Ma foi, oui, pourquoi pas. Ce qui me trotte dans la tête en ce moment c’est de pondre moi-même des algos de datamoshing en Python. Si j’y arrive j’en ferai un article…

    @dway : Vous êtes exclu du lycée pendant les 3 prochains jours. L’exclusion définitive vous pend au nez.

  • Icefo

    Ahhhh pyqt

    J’en ai aussi chié un moment avant de réussir à faire quelque-chose d’utile. J’ai beaucoup aimé traduire la doc et les exemple obscures en C++ vers python.

  • e-jambon

    Super article… C’est génial et couillu cette façon de se présenter — et c’est tellement plus vrai que la majorité des trucs qu’on voit sur le net, genre “c’est super simple, je vois pas pourquoi vous galérez. Moi ça m’a pris 15 secondes pour tout faire marcher du premier coup. Si vous n’y arrivez pas, c’est parce que vous êtes pas le quart de la moitié aussi intelligent que moi”.

    La vraie classe.

  • Sam

    “J’avais un peu peur de rendre le truc utile, alors je l’ai laissé en l’état” m’a tué :)

  • kontre

    T’as bien fait de mettre un gif, la version en JS chie comme prévu ! Il manque 5 cases à la dernière ligne donc je suppose que c’est la grille qui n’a pas gardé la bonne forme.

    J’aime particulièrement la version haute résolution avec les cases qui se chevauchent.

  • 01ivier Post author

    @e-jambon : Merci… :-)

    Je me dis qu’être honnête c’est un moyen comme un autre de ne pas passer pour un con… :-p

    De toutes façons, je voudrai jouer le gars qui touche sa bille que je me ferai démasquer en 2 commentaires…

    @kontre : En fait, il n’y a pas de grille. Les input s’empilent tous seuls dans une div qui a une taille fixée en dur en fonction de la dimension souhaitée des cases (14×14).

    Mais, il n’est pas impossible que ton navigateur de permette pas de descendre la taille des checkbox jusqu’à cette valeur.

    Où que tes préférences d’affichages effectuent un petit zoom-out…

    Dans ce cas, la remarque de @d.j.a.y devrait résoudre le problème.

  • Recher

    Ahh ahhh ! Mes yeux ! Mes yeux fondent !

    Voici ma wish-list pour tes prochains créations :

    un visualiseur de son avec des scrolls bar. (Le son est joué, les scroll-bar montent et descendent selon les fréquences sonores)
    un jeu de Simon (https://fr.wikipedia.org/wiki/Simon_%28jeu%29) avec les diodes “Verr Num”, “Scroll Lock” et “Caps Lock” du clavier.
    un visualiseur de flux cartographique (OpenStreetMap, GoogleMap, …) en Ascii-art / libcaca.
    un émetteur morse avec les bips de l’ordinateur
    une machine à beurrer les tartines avec une imprimante (mais je crois que Gaston Lagaffe avait déjà inventé un premier prototype à partir d’une machine à écrire).

  • 01ivier Post author

    @Recher : Que de bonnes idées ! :-)

    Pour le visualiseur de son avec des scrolls bar, j’ai fait un truc dans le genre il n’y a pas longtemps.

    Il s’agissait alors de faire des vagues avec des sliders dans Puredata (ce n’est pas du Python, donc, mais ça commence par la même lettre :-p )

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.