Une interface graphique peut permettre à un utilisateur d’interagir avec un programme. Dans cet article, nous allons voir comment intégrer OpenCV dans une interface graphique créée avec Tkinter.

Tkinter (Tk interface) est une bibliothèque permettant de créer des interfaces graphiques en Python. Bien que la documentation de Tkinter n’est pas une des plus fournie, il existe un grand nombre de tutoriel permettant de l’utiliser (python-django, fsincere, developez)

Dans cet article, nous allons mettre en place une interface graphique permettant de choisir un seuil et un type de seuillage, pour trouver les paramètres optimaux afin de seuiller une image en niveau de gris. Cette application peut particulièrement être utile pour la mise en place de la méthode de détection de formes ou de couleurs.

Interface que l’on souhaite réaliser

Tkinter étant intégré à Python, nous n’aurons pas à l’installer. Cependant, nous devons installer la bibliothèque Pillow puisque Tkinter en aura besoin pour afficher des images dans notre interface graphique :

pip3 install pillow

Une fois cette bibliothèque installée, nous pouvons commencer à créer notre programme.

Création d’une interface graphique avec Tkinter

Comme toujours, la première étape consiste à importer les bibliothèques nécessaires. Nous aurons besoin pour notre application de divers modules : Tkinter, Pillow et Opencv

from tkinter import 
from tkinter.filedialog import *	
from PIL import Image
from PIL import ImageTk
import cv2

Définition des fonctions

Nous allons maintenant définir deux fonctions : clicRechercher et clicTraiter. La première permettra de rechercher une image, de l’afficher dans le cadre image originale et d’afficher également une image en niveau de gris dans le cadre image seuillée. La seconde aura pour but de récupérer les variables pour le seuillage et de l’effectuer.

Recherche et chargement de l’image

Commençons par la première fonction :

def clicRechercher():
	global Frame1, Frame2, gray
	
	filepath = askopenfilename(title="Ouvrir une image")

	if len(filepath)>0:
		img=cv2.imread(filepath)
		w=300
		img = cv2.resize(img, (w,int((w/img.shape[1])*img.shape[0])), 0, 0, cv2.INTER_NEAREST )

		img_RGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
		gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

		#Affichage image originale
		img_RGB = Image.fromarray(img_RGB)
		img_RGB = ImageTk.PhotoImage(img_RGB)
		Frame1.configure(image=img_RGB)
		Frame1.image=img_RGB
		
		#Affichage image niveau de gris
		graydisp = Image.fromarray(gray)
		graydisp = ImageTk.PhotoImage(graydisp)
		Frame2.configure(image=graydisp)
		Frame2.image=graydisp

Nous déclarons, à la ligne 8, les variables globales que nous utiliserons dans la fonction. Frame1 correspond à l’emplacement de l’image d’origine et Frame2 à celui de l’image traitée. Nous définissons également gray correspondant à la conversion en niveau de gris de notre image originale.

Nous appelons ensuite la fonction askopenfilename (l. 10) de Tkinter qui ouvre une nouvelle fenêtre permettant de sélectionner l’image que nous souhaitons traiter.

La ligne 12, permet de s’assurer qu’une image à bien été sélectionnée, puis nous lisons l’image à la ligne 13.

Afin de garantir l’aspect visuel de notre application, nous redimensionnons l’image d’origine (l. 15) pour s’assurer que la largeur (w) soit de 300 pixels (l.14).

Une fois notre image redimensionnée, nous réalisons deux conversions colorimétriques. La première (l. 17) permet d’obtenir l’image dans le domaine RGB (OpenCV traite des images BGR) et la seconde (l. 18) permet d’obtenir l’image en niveau de gris.

Nous pouvons enfin afficher nos deux images. Nous convertissons les images du format OpenCV au format PIL/Pillow (l. 21 et 27) puis au format ImageTk (l. 22 et 28). Nous pouvons maintenant afficher nos deux images en appelant la fonction configure de chaque panneau (l. 23 et 29), puis en spécifiant l’image que nous affichons (l. 24 et 29).

Seuillage de l’image

Passons maintenant à notre deuxième fonction:

def clicTraiter():
	global val, Seuillage

	seuil = val.get()
	seuillage=Seuillage.get()

	ret,thresh = cv2.threshold(gray,seuil,255,seuillage)

	thresh = Image.fromarray(thresh)
	thresh = ImageTk.PhotoImage(thresh)
	Frame2.configure(image=thresh)
	Frame2.image=thresh

Nous commençons par déclarer deux variables globales : val et Seuillage qui nous permettent de récupérer les variables pour le seuillage. Nous récupérons donc la valeur du seuil à la ligne 35 et le type de seuillage utilisé à la ligne 36.

Une fois ces informations récupérées, nous pouvons effectuer le seuillage ligne 38, puis nous pouvons afficher l’image seuillée (l. 40-43).

Définition de l’interface

Nos fonctions étant prêtes, nous pouvons passer au codage le l’interface en elle-même.

fenetre = Tk()
fenetre.geometry("620x600")
fenetre.title("Seuillage d'image")

P=PanedWindow(fenetre, orient=VERTICAL)
P.pack(side=TOP, expand=Y, fill=BOTH)

Nous commençons par définir notre fenêtre (l. 45), ainsi que sa taille (l. 46) et son nom (l. 47).

Puis nous rentrons dans le remplissage de cette fenêtre. Nous commençons par définir un widget PanedWindow, situé dans la fenêtre et orienté verticalement. PanedWindow est un conteneur, qui contiendra les différents éléments de notre application.

Cadre de chargement d’image

Chargement = LabelFrame(P, text="Chargement d'une image")
boutonFile = Button(Chargement,text="Rechercher image", command=clicRechercher)
boutonFile.pack(pady=5)
P.add(Chargement)

Le premier élément que nous ajoutons est le bouton de chargement de l’image. Ce bouton sera situé dans un cadre disposant du label « Chargement d’une image » (l. 52). Le bouton (l. 53), quant à lui portera le texte « Rechercher image » et, une fois que l’utilisateur aura cliqué dessus, appellera la fonction clicRechercher. Enfin, nous ajoutons le LabelFrame chargement à notre PanedWindow.

LabelFrame Chargement

Cadre de réglages

Le second cadre que nous créons est celui permettant le réglage du seuillage. Nous définissons donc un cadre comprenant le label « Réglages » (l. 57) et une PanedWindow, (l. 58) horizontale cette fois, qui contiendra un cadre pour le choix du seuil et un cadre pour le choix du type de seuillage utilisé.

Reglage = LabelFrame(P, text="Réglages", padx=5, pady=5)
PReglage = PanedWindow(Reglage, orient=HORIZONTAL)

frameSeuil=Frame(PReglage)
val=IntVar()
Scale(frameSeuil, variable=val,from_=0, to=255,orient=HORIZONTAL,tickinterval=50, length=200, label='Valeur du seuil').pack()
PReglage.add(frameSeuil)

barre = Frame(PReglage,height=100,width=2,bg="black")
barre.pack( )
PReglage.add(barre)

frameSeuilage=Frame(PReglage)
Label(frameSeuilage, text="Type de seuilage").pack()
Seuillage = IntVar()
R1 = Radiobutton(frameSeuilage, text="Binaire", variable=Seuillage, value=0).pack( anchor = W)
R2 = Radiobutton(frameSeuilage, text="Binaire Inversé", variable=Seuillage, value=1).pack( anchor = W)
R3 = Radiobutton(frameSeuilage, text="Tronqué", variable=Seuillage, value=2).pack( anchor = W)
R4 = Radiobutton(frameSeuilage, text="Seuil à 0", variable=Seuillage, value=3).pack( anchor = W)
R5 = Radiobutton(frameSeuilage, text="Seuil à 0 inversé", variable=Seuillage, value=4).pack( anchor = W)
PReglage.add(frameSeuilage)
PReglage.pack()

Button(Reglage,text="Traiter", command=clicTraiter).pack(padx=5,pady=5)
Reglage.pack()
P.add(Reglage)

Commençons par le premier canevas (l. 60-63). Nous déclarons la variable val, qui nous permettra de récupérer la valeur du seuil. Pour cela, nous appelons le widget Scale, qui permet de créer une barre horizontale, avec un curseur permettant de sélectionner une valeur entre 0 et 255. Et nous l’ajoutons à la PanedWindow « PReglage ».

Pour améliorer l’apparence de notre application, nous ajoutons une barre verticale (l. 65-67) permettant de séparer la sélection du seuil et le choix du type de seuillage.

Enfin, pour finir la partie réglage du seuillage, nous proposons à l’utilisateur de choisir entre différents types de seuillage. Pour ce faire, nous utilisons 5 widgets Radiobutton (l. 72-76), correspondant à 5 types de seuillage. Si vous souhaitez savoir à quoi correspondent ces 5 types de seuillage, vous pouvez vous rendre sur cette page. Le type de seuillage pouvant être sélectionné dans la fonction cv2.threshold, grâce à un entier, est stocké dans la variable Seuillage, sous la forme d’un entier compris entre 0 et 4. Nous ajoutons, pour finir, ces boutons à la PanedWindow « PReglage ».

Pour finir la partie réglage, nous ajoutons un bouton « Traiter » (l. 80) qui permet, comme son nom l’indique de lancer le seuillage de l’image, en appelant la fonction clicTraiter.

Cadre d’affichage

Le dernier cadre de notre application est le cadre permettant d’afficher les images et il portera donc le nom « Affichage » (l. 84).

PanelB=LabelFrame(P,text = "Affichage", width=500, height=120)
Frame1 = Label(PanelB)
Frame1.pack(padx=5, pady=5,side=LEFT)
Frame2 = Label(PanelB)
Frame2.pack(padx=5, pady=5,side=RIGHT)
PanelB.pack()
P.add(PanelB)

fenetre.mainloop()

Ce cadre sera séparé en deux parties. Un premier cadre, Frame1, permettra d’afficher l’image d’origine et sera situé à gauche (l. 85-86). Le second, Frame2, sera utilisé pour afficher l’image seuillée et sera positionné à droite (l. 87-88).

Pour finir, nous lançons notre application à la ligne 92 grâce à l’appel de la fonction mainloop()

Lancement de l’application

Maintenant que notre programme python est fini, nous pouvons le lancer:

python3 application.py
C’est pas joli, mais ça fait ce que l’on demande !

Nous pouvons ensuite charger une image :

Et tester différents seuils 🙂

Je profite de cet article pour expliciter les problèmes de certaines personnes lors de l’application de l’article sur la reconnaissance des formes. En effet, nous pouvons voir qu’à seuil identique les images originales et compressées ne donnent pas le même résultat. Il est alors nécessaire d’adapter le seuil à chaque situation.


0 commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.