Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 05/03/2023, à 21:00

cyril_840

Interface tkinder et camera

Bonjour, je suis en train de faire une interface en python qui permet de choisir la camera souhaité et l'afficher. Mais je bute sur un problème.
Le code se compose d'une fenetre principale root et d'une fenetre secondaire qui s'ouvre lorsqu'on appuye sur le bouton "source" qui se trouve sur la fenetre principale. Je n'arrive pas a récuperer correctement l'info de la listbox qui se trouve sur la fenetre secondaire "source".

Je bute également sur l'affichage du canvas, l'image de la camera ne se met pas à jour comme souhaité.

Voici mon code a ce stade

import cv2
from tkinter import *
from PIL import Image, ImageTk

camera_listbox = None  # Déclaration de la variable globale
source = None  # Déclaration de la variable globale
canvas = None
cap = None  # variable pour la capture vidéo

def create():
    global  source , camera_listbox # Utilisation des variables globales

    # Vérifier si la fenêtre source existe déjà, pour ne pas la reouvrir
    if source and source.winfo_exists():
        source.lift() #met la fenetre source en premier plan
        return

    source = Toplevel(root)
    source.geometry('400x400')
    mylabel = Label(source,text = 'Choisir la caméra')
    mylabel.pack(pady=10)

     #lister camera et creer une listbox
    camera_indexes = []
    for i in range(10):
        cap = cv2.VideoCapture(i)
        if cap.isOpened():
            camera_indexes.append(i)
            cap.release()

    # Ajout de la liste des caméras à la fenêtre
    if camera_indexes:
        mylabel2 = Label(source, text="Caméras disponibles : ")
        mylabel2.pack()

        camera_listbox = Listbox(source)
        for index in camera_indexes:
            camera_listbox.insert(index, f"Caméra {index}")
        camera_listbox.pack()
    else:
        mylabel3 = Label(source, text="Aucune caméra disponible")
        mylabel3.pack()
    #bouton enregistrer
    bouton_enregistrer = Button(source, text="Enregistrer", command = enregistrer)
    bouton_enregistrer.pack(pady = 10)
    # bouton quitter
    bouton_quitter = Button(source, text="quitter", command=quitter)
    bouton_quitter.pack(pady=10)

def enregistrer():
    global camera_listbox
    print("enregistrer")
    print(camera_listbox.curselection()[0])

def quitter():
    global source
    print("fermeture source!")
    source.destroy()

def hello():
    print("hello world!")

def fermeture():
    print("fermeture root!")
    root.quit()

def flip_img_horizontal():
    print("flip horizontal")

def flip_img_vertical():
    print("flip vertical")


def show_frame():
    ret, frame = cap.read()
    if ret:
        # Convertir l'image de format BGR en format RGB
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        # Créer une image Tkinter à partir de l'image OpenCV
        img = Image.fromarray(frame)
        imgtk = ImageTk.PhotoImage(image=img)

        # Afficher la vidéo en direct dans le canevas Tkinter
        canvas.imgtk = imgtk
        canvas.create_image(0, 0, anchor=NW, image=imgtk)

    # Appeler la fonction "show_frame" toutes les 15 millisecondes
    root.after(15, show_frame)

root = Tk()
root.geometry('800x600')
root.title("Camera Interface")
root.protocol("WM_DELETE_WINDOW", fermeture)
btn = Button(root, text="Source", command = create)
btn.pack(pady = 10, side='right')
btn = Button(root, text="Inverser horizontal", command = flip_img_horizontal)
btn.pack(pady = 10, side='right')
btn = Button(root, text="Inverser vertical", command = flip_img_vertical)
btn.pack(pady = 10, side='right')
# Créer le canevas Tkinter pour afficher la vidéo
canvas = Canvas(root, width=640, height=480)
canvas.pack()

# Initialiser la capture vidéo
cap = cv2.VideoCapture(1)
# Appeler la fonction "show_frame" pour mettre à jour l'affichage vidéo
show_frame()
root.mainloop()

Je vais poster des images pour que vous puissiez mieux comprendre a quoi sa ressemble:
http://electronika.epizy.com/bug/root.jpg

http://electronika.epizy.com/bug/source.jpg

Dernière modification par cyril_840 (Le 05/03/2023, à 21:13)

Hors ligne

#2 Le 09/03/2023, à 03:30

kholo

Re : Interface tkinder et camera

salut,
je ne code pas des masses en ce moment mais je vais essayer de te donner quelques tuyaux.
je vais mettre du code en dessous que tu peux récupérer tel quel dans un script et l'exécuter pour voir ce que cela fait.

Note
pour ceux qui suivraient, cv2 est open cv et est dans les dépôts de Ubuntu (au moins la 20.04)

sudo apt install python3-opencv

pour ce qui est de ton code :
évite les variables globales...
tu as des façons simples de jouer et d'injecter des variables.
personnellement, j'aime les dictionnaires mais ils sont un peu compliqués quand on s'y met
mais si tu veux faire simple, tu as les listes et les tuples
tu peux faire des listes de listes ou des listes de tuples ou des listes de listes de... ce que tu veux
Ensuite, pour récupérer ta variable d'une fonction, tu mets un return
exemple :

## code_pas_cool
une_variable = "je suis du texte"
def une_fonction_avec_global () :
    global une_variable
    print(une_variable)

une_fonction_avec_global()
## code un peu plus cool
def une_fonction_sans_global (une_variable) :
    print(une_variable)
    return une_variable + " modifié"

une_variable = "je suis du texte"
une_variable = une_fonction_sans_global(une_variable)
print("le retour est " + une_variable)

... et peu importe que ta variable dans le code principal et dans la fonction aient le même nom



à la fin de ton code, il est bon d'avoir un petit :

if __name__ == "__main__":
    pass

en soit, tel quel, ça ne sert à rien. L'idée est surtout de partir d'un squelette puis de modifier selon les besoins
voilà un de mes squelettes :

#!/usr/bin/env python3
# -*- coding: UTF8 -*-
"""
module docstring

"""

__module__ = "xxxxxxxxxx"

__title__ = "xxxxxxxxxx"
__author__ = 'xxxxxxxxxx'
__license__ = 'GNU'
__copyright__ = 'GNU 2021 07 24'
__ver_major__ = 0
__ver_minor__ = 1
__ver_patch__ = 0
__ver_sub__ = ''
__version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__,
                              __ver_patch__, __ver_sub__)

## LE CODE EST A METTRE ICI

if __name__ == "__main__":
    pass

tu verras dans les codes en dessous comment je m'en sert

... ensuite

from tkinter import *

n'est pas une bonne habitude... il est préférable de nommer les modules
tu peux (par exemple) soit :
importer les objets nommément

from tkinter import Button, Toplevel,........

soit importer tout le module en utilisant un alias (plus souvent utilisé)

import tkinter as tk

pour rester dans tkinter, je ne suis pas fan de pack
il semble plus simple pour les débutants mais en fait pas tant que ça...
et vu comme tu as placé tes objets, je pense que tu peux attaquer grid sans faire pire qu'avec pack

ensuite, ce sera plus propre si tu codes en POO plutôt qu'en Fonctionnel
une fois lancé là dedans, tu peux commencer à jouer avec des trucs plus poussés comme Toplevel
je te mets un squelette que je m'étais fait pour m’entraîner :

#!/usr/bin/env python3
# -*- coding: UTF8 -*-

import tkinter as tk
import tkinter.ttk as ttk

"""
démo pour lancement de fenêtre secondaire modale
utilité de 
    Toplevel
    wait_window
    grab_set
    transient
"""

class Principale(tk.Tk):
    def __init__(self):
        super().__init__()
        self.titre = 'fenetre principale'
        self.geometry("250x250-100-100")

        self.bouton1 = tk.Button(self, text="ok1", height=3, width=3)
        self.bouton1.grid(row=0, column=0)
        self.bouton1.bind('<ButtonPress>', self.click_ok)

        self.bouton2 = tk.Button(self, text="ok2", height=3, width=3)
        self.bouton2.grid(row=0, column=1)
        self.bouton2.bind('<ButtonPress>', self.click_ok_sans_wait)
        
        self.bouton3 = tk.Button(self, text="quit", height=3, width=3)
        self.bouton3.grid(row=0, column=2)
        self.bouton3.bind('<ButtonPress>', self.click_quit)
        
        self.mainloop()

    def click_ok(self, event):
        print(30*'-')
        self.fen_sec = Secondaire(self)
        self.wait_window(self.fen_sec.gui_cn)
        print("coucou")

    def click_ok_sans_wait(self, event):
        print(30*'-')
        self.fen_sec = Secondaire(self)
        # pas de wait ici !!!
        print("coucou")

    def click_quit(self, event):
        print(30*'-')
        print("click sur le bouton quitter")
        self.destroy()

class Secondaire:
    def __init__(self, master):
        self.gui_cn = tk.Toplevel(master)
        self.gui_cn.title("fenetre secondaire")
        self.gui_cn.geometry("250x70-100+50")

        self.gui_cn.wait_visibility() # nescessaire pour grab_set
        self.gui_cn.grab_set() # empèche les modifs de la fenetre parente
        self.gui_cn.transient(master) # garde la popup au dessus

        self.bouton = tk.Button(self.gui_cn, text="ok", height=3, width=3)
        self.bouton.grid(row=0, column=0)
        self.bouton.bind('<ButtonPress>', self.click_ok)

        # PAS DE MAINLOOP SUR LA FENETRE SECONDAIRE ...

    def click_ok(self, event):
        print("click sur le bouton OK secondaire")
        self.gui_cn.destroy()

if __name__ == "__main__":
    Principale()

et donc tu vois la création de la classe tout à la fin ( > Principale() < ):

if __name__ == "__main__":
    Principale()

qui signifie : si le module dans lequel je suis est le module principal alors exécute... Principale()
pour la sémantique on est d'accord que Principale() est une instance de la classe
inversement, si tu importes ce module dans un autre module, alors tu pourra appeler les classes mais ce qui est dans le dernier IF ne sera pas exécuté.
Pour mieux comprendre si tu retires les deux lignes

if __name__ == "__main__":
    Principale()

et que tu lances le module, il ne se passera rien (à l'écran)

NB : pour les boutons

        self.bouton3 = tk.Button(self, text="quit", height=3, width=3)
        self.bouton3.grid(row=0, column=2)
        self.bouton3.bind('<ButtonPress>', self.click_quit)

à la place de :

        self.bouton3 = tk.Button(self, text="quit", height=3, width=3, command = self.click_quit)
        self.bouton3.grid(row=0, column=2)

on peut mettre la commande directe dans la ligne de création... mais l'avantage d'un bind est de plus facilement gérer les déclenchements... et ça fait des lignes plus courtes....


Une fois que tu auras vu cela, tu pourras commencer par éclater ton code pour le tester par morceaux puis le réunir.
et tu pourras tester des objets comme les listbox par exemple :

#!/usr/bin/env python3
# -*- coding: UTF8 -*-
""" fenêtre Toplevel de choix dans un combo rempli par une liste """

import tkinter as tk
import tkinter.ttk as ttk

class Principale(tk.Tk):
    def __init__(self):
        super().__init__()
        self.titre = 'fenetre principale'
        self.geometry("250x100-250-250")

        self.bouton1 = tk.Button(self, text="Choisir", height=2, width=5, command=lambda:self.choisir(self))
        self.bouton1.grid(row=0, column=0)

        self.bouton2 = tk.Button(self, text="quitter", height=2, width=5)
        self.bouton2.grid(row=0, column=1)
        self.bouton2.bind('<ButtonPress>', self.click_quit)

        self.mainloop()

    def choisir(self, parent):
        """ Ouvre une fenêtre modale """
        data = ["le premier", "le second", "le troisième", "cat"]

        # mode = tk.SINGLE
        mode = tk.EXTENDED

        self.result = Choisir(self, data, le_mode=mode, defaut=[0, 2])
        self.result.transient(self)
        self.result.grab_set()
        self.wait_window(self.result)

        retour = [data[x] for x in self.result.le_retour]
        print("informations en sorties", retour)

    def click_quit(self, event):
        print(30*'-')
        print("click sur le bouton quitter")
        self.destroy()

class Choisir(tk.Toplevel): 
    """ defaut est une liste de sélection par index"""
    def __init__(self, parent, une_liste=["vide"], le_mode=tk.SINGLE, defaut=[0,]):
        """ méthode d'initialisation """
        super().__init__(parent)
        self.valeurs = une_liste
        self.le_retour = defaut
        # ---------------------------------------
        self.title("Faites votre choix")
        self.geometry("450x150-250+150")
        # ---------------------------------------
        frame_btn = tk.Frame(self)
        frame_btn.grid(row=0, column=0, sticky="nw")
        tk.Button(frame_btn, text="Annuler", command=self.annuler).grid(row=0, column=0, sticky="w")
        tk.Button(frame_btn, text="Valider", command=self.valider).grid(row=0, column=1, sticky="w")
        # ---------------------------------------
        tk.Label(self, text="Choix : ").grid(row=1, column=0, sticky="nw")
        la_font = ('Ubuntu', 11)

        # le_mode = tk.SINGLE
        # le_mode = tk.EXTENDED

        self.list_champ = tk.Listbox(self, font=la_font, selectmode=le_mode)
        for value in self.valeurs:
            self.list_champ.insert(tk.END, value)

        """
        selection_set(debut, fin=None)
        Sélectionne toute les lignes dont les index appartiennent à l’intervalle [debut, fin]. 
        Si le deuxième argument est omis, seule la ligne d’index debut est sélectionnée.
        """
        for selection in defaut:
            self.list_champ.selection_set(selection)

        # self.list_champ.current(0)
        self.list_champ.grid(row=2, column=0, sticky="nsew")
        # ---------------------------------------
        self.grid_rowconfigure(0, weight=0)
        self.grid_rowconfigure(1, weight=0)
        self.grid_rowconfigure(2, weight=15)
        self.grid_columnconfigure(0, weight=1)
        self.resizable(True, True) 
    def valider(self):
        """ méthode du bouton valider """
        # self.le_retour = self.list_champ.get()
        self.le_retour = self.list_champ.curselection()
        self.destroy()

    def annuler(self):
        """ méthode du bouton annuler """
        self.destroy()


if __name__ == '__main__':
    Principale()

NB : il reste encore du boulot pour injecter les données (pour toi les caméras)
... et justement en parlant d'injection de données, il est bon de commencer à travailler sur les données et ensuite seulement sur l'interface graphique.
Là, c'est plus compliqué à expliquer et montrer sans te faire tout ton code... pas que je ne veuille pas mais j'ai pas des masses de temps... mais on peut commencer par un début...

d'abord tu veux comprendre comment fonctionne la caméra et cela tu l'as déjà pas mal avancé...
ensuite, quand tu commences à faire ta GUI, tu fais des tests pour voir comment fonctionnent les objets
ça tu as maintenant mes deux bouts de scripts.
ensuite, tu mets tout ensemble.

voilà... si ça tu arrives à comprendre ce que j'ai mis comme code, on peut aller plus loin... et d'autres vont certainement ajouter plein d'autres choses sympas à faire et savoir...
si je t'ai ennuyé, désolé... code comme tu le sens ; l'important est d'y prendre du plaisir !

Hors ligne

#3 Le 07/04/2023, à 19:15

cyril_840

Re : Interface tkinder et camera

Je viens de voir ta réponse, merci d'avoir prix le temps de tout écrire...

Je vais étudier la programmation objet proposé...

J'ai déja essayé ton code, le dernier fonctionne comme je le souhaite, merci

J'ai tendance a préférer programmer sous forme fonctionnelle, j'ai plus de mal en programmation objet.

smile

Hors ligne