La première chose à faire est d'inclure les packages python permettant par la suite d'utiliser :
import cv2
import numpy as np
import matplotlib.pyplot as plt
#...
Attention :
une image couleur sera représentée en python par un tableau n-dimensions Numpy où n vaudra 3.
une image en niveaux de gris sera aussi représentée en python par un tableau n-dimensions Numpy mais n vaudra 2.
Création d'une image
Créer une image BGR vide avec numpy
img = np.zeros((height,width,3), np.uint8)
Créer une image BGR vide aux mêmes dimensions qu'une autre image
img2 = np.zeros_like(img)
Dimensions d'une image
Les dimensions de la matrice nd représentant l'image sont accessible de la façon suivante :
img.shape
Cet attribut est un tuple python comprenant de 2 à 3 valeurs. Les deux premières valeurs représentent les dimensions de l'image : hauteur et largeur. La dernière valeur décrit le nombre de composantes permettant de représenter les couleurs.
Si l'image n'a qu'une seule composante par pixel alors le tuple ne comportera que 2 valeurs.
Accès aux données pixels
Pour accéder aux données couleur d'un pixel :
(b, g, r) = img[100, 100]
Pour modifier les données couleur d'un pixel :
img[100,100] = [255,255,255]
Pour accéder aux données d'une image en niveaux de gris (une seule composante) :
gray = img[100,100]
Pour modifier les données d'une image en niveaux de gris (une seule composante) :
img[100,100] = 196
Séparation/fusion des différentes composantes
Séparer les différentes composantes couleur d'une image :
(B, G, R) = cv2.split(img)
Après cet appel, B, G, R seront des images de même dimension que img mais ne comportant qu'une seule composante. B (resp. G et R) contient l'ensemble des valeurs de la composante bleu (resp. vert et rouge) de chaque pixel de l'image d'origine.
Combiner les images des différentes composantes pour former une image couleur :
img = cv2.merge((B,G,R))
Attention, cette fonction prend un tuple contenant les images des différentes composantes. D'où le double parenthésage.
Conversion entre espaces de couleur
Pour convertir une image entre deux espaces de couleur, il faut utiliser la fonction cv2.cvtColor(). Cette fonction prend 2 paramètres :
par exemple, pour passer de l'espace BGR à un niveau de gris :
res = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
Les différents codes de conversion disponibles sont décrit ici.
Redimensionnement d'une image
Pour redimensionner une image, il faut utiliser la fonction OpenCV cv2.resize(s, size,fx,fy,interpolation). Cette fonction prend de 2 à 5 paramètres :
Pour afficher une image, il est possible d'utiliser la fonction native d'OpenCV cv2.imshow(name,image).
Cette fonction prend 2 paramètres :
Attention : par défaut, OpenCV encode les images en Bleu-Vert-Rouge. L'affichage de l'image via OpenCV prend en compte cela et donc les images s'affichent correctement.
import cv2
import numpy as np
import sys
# création de l'image
img = np.zeros((480,640,3), np.uint8)
cv2.namedWindow('image originale')
cv2.imshow('image originale',img)
cv2.destroyWindow('image originale')
La fonction cv2.destroyWindow permet de fermer la fenêtre OpenCV dont le nom est passé en argument. Pour fermer l'ensemble des fenêtres en un seul appel, il faut utiliser la fonction OpenCV cv2.destroyAllWindows qui ne prend aucun paramètre.
La fonction cv2.imshow n'est pas bloquante, elle rend la main à l'appelant une fois l'image affichée. Ainsi l'exécution de ce programme se termine dès l'affichage de la fenêtre.
Il est donc nécessaire de "bloquer" manuellement l'exécution du programme après l'affichage en attendant l'appuie sur une touche du clavier. Ceci peut être fait en utilisant la fonction cv2.waitKey(delai). Cette fonction prend un seul paramètre : le temps (en milliseconde) d'attente avant de rendre la main au programme. Une valeur de 0 fera que la fonction attendra indéfiniement.
Si une touche est pressée avant la fin du délai d'attente, la fonction va rendre la main au programme en renvoyant le code de la touche pressée.
Attention : la fonction cv2.waitKey ne fonctionnera que si une fenêtre OpenCV est affichée.
import cv2
import numpy as np
import sys
# création de l'image
img = np.zeros((480,640,3), np.uint8)
cv2.namedWindow('image originale')
cv2.imshow('image originale',img)
key = cv2.waitKey(0) & 0x0FF
if key == 27:
print('arrêt du programme par l\'utilisateur')
cv2.destroyWindow('image originale')
sys.exit(0)
Dans l'exemple ci-dessus, l'attente se poursuit indéfiniement tant que l'utilisateur n'appuie pas sur une touche.
Une fois que l'utilisateur appuie sur une touche, le programme récupère le code ASCII correspondant (via le ET binaire avec la valeur 0x0FF). Si la touche appuyée est la touche échap (code 27) alors le programme se termine sinon il passe à la suite.
Il est possible de prendre en compte la touche appuyée afin de réaliser différents traitements. Pour cela, il est nécessaire de placer la gestion des touches dans une boucle infinie :
import cv2
import numpy as np
import sys
def traitement1(image):
# resultat = ...
cv2.imshow('image originale',image)
# création de l'image
img = np.zeros((480,640,3), np.uint8)
cv2.namedWindow('image originale')
cv2.imshow('image originale',img)
while True:
key = cv2.waitKey(30) & 0x0FF
if key == 27 or key==ord('q'):
print('arrêt du programme par l\'utilisateur')
break;
if key ==ord('t'):
traitement1(img)
cv2.destroyWindow('image originale')
Ecrire un programme python permettant de créer et afficher une image couleur (3 composantes) avec les dimensions suivantes :
Le programme devra proposer les interactions suivantes :
Il est possible d'intégrer, dans une fenêtre OpenCV, des sliders afin de définir la valeur de certains paramètres.
Pour créer un slider, il faut utiliser la fonction cv2.createTrackbar : cv2.createTrackbar(slidername,windowName,currentValue,maxValue,callbackFunction,userData)
Dans cette fonction :
Attention : la valeur gérée par le slider est forcément une valeur entière comprise dans l'intervalle [0,maxValue].
La fonction de réponse callbackFunction prend 2 arguments si userData a été défini :
Si userData n'a pas été défini alors la fonction callbackFunction prend un seul argument qui correspond à la valeur du slider.
import cv2
import numpy as np
def traitement1():
image[...] = valeur
cv2.imshow('image originale',image)
def fonctionTrackbar(v):
global valeur
valeur = v
traitement1()
image = np.zeros((480,640,3), np.uint8)
valeur = 100
cv2.namedWindow('image originale')
cv2.createTrackbar('Valeur','image originale',valeur,255,fonctionTrackbar)
traitement1()
while True:
key = cv2.waitKey(30) & 0x0FF
if key == 27 or key==ord('q'):
print('arrêt du programme par l\'utilisateur')
break;
cv2.destroyWindow('image originale')
Le programme précédent va remplir intégralement l'image avec la valeur définie par le slider.
Attention : notez l'utilisation du mot-clé global dans la fonction fonctionTrackbar. Sans cette ligne de code, python considérera la variable valeur comme une variable locale de la fonction et ne modifiera donc pas la valeur de la variable globale. Cette ligne n'est cependant pas nécessaire dans la fonction traitement1 puisque valeur est utilisée en partie droite d'une affectation. Il en va de même pour la variable image. Comme elle ne fait pas l'objet d'une affectation directe (du type image=...) alors python comprend qu'il s'agit d'une variable globale.
A partir du code de l'exercice précédent, modifier le programme afin que :
Le rectangle devra être redessiné à chaque modification d'un slider si le dessin est activé.
Il est possible d'interagir avec la souris dans une image. Ceci se fait en définissant une fonction de callback pour gérer les évènements souris en utilisant la fonction cv2.setMouseCallback : cv2.setMouseCallback(windowName,callbackFunction)
Dans cette fonction :
La fonction de callback prend 5 paramètres :
import cv2
import numpy as np
# création de l'image
image = np.zeros((480,640,3), np.uint8)
draw = False
def drawCallback(event,x,y,flags,data):
global draw
if event==cv2.EVENT_LBUTTONDOWN:
draw = True
image[y,x] = [255,255,255]
elif event==cv2.EVENT_LBUTTONUP:
draw=False
elif event==cv2.EVENT_MOUSEMOVE:
if draw:
image[y,x] = [255,255,255]
cv2.imshow('image originale',image)
cv2.namedWindow('image originale')
cv2.setMouseCallback('image originale',drawCallback)
cv2.imshow('image originale',image)
while True:
key = cv2.waitKey(30) & 0x0FF
if key == 27 or key==ord('q'):
break;
print('arrêt du programme par l\'utilisateur')
cv2.destroyWindow('image originale')
Le programme précédent permet de dessiner en blanc sur une image noire. L'appuie sur le bouton gauche de la souris active le dessin. Le relachement de ce même bouton désactive le dessin. Quand la souris bouge, on vérifie si le dessin est activée ou pas.
A partir du code de l'exercice précédent, modifier le programme afin que :
Le rectangle devra être redessiné à chaque modification d'un slider si le dessin est activé.
Pour ouvrir un fichier image et charger les données correspondantes, il faut utiliser la fonction cv2.imread(fichier,options). Cette fonction prend 2 paramètres :
Cette fonction retourne les données image dans un tableau numpy (numpy.ndarray).
Si le fichier ne peut être ouvert ou si les données ne peuvent être chargées, la fonction renverra None.
import cv2
import numpy
img = cv2.imread('Pictures/Camera Roll/WIN_20220325_17_38_19_Pro.jpg')
if img is None:
print('impossible de charger les données du fichier')
sys.exit(-1)
#...
Par défaut, OpenCV charge les données en couleur au format BGR en 8bits par composante. Dans le cas d'une image en niveaux de gris, elle sera quand même considérée comme une image couleur BGR mais les 3 composantes seront identiques.
L'appel : img = cv2.imread('...',cv2.IMREAD_GRAYSCALE) permet de lire l'image directement comme une image en niveau de gris. De cette manière img sera une image à une seule composante.
Pour enregistrer une image sur le disque, il faut utiliser la fonction cv2.imwrite(fichier,image,options). Cette fonction prend 2 à 3 arguments :
import cv2
import numpy
#...
cv2.imwrite('Pictures/Camera Roll/WIN_20220325_17_38_19_Pro.jpg', img)
Afin de pouvoir utiliser cette fonction, il est nécessaire d'ajouter dans le préambule du fichier :
import io
def plotToImage(fig, dpi=180):
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=dpi)
buf.seek(0)
img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)
buf.close()
img = cv2.imdecode(img_arr, 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img
def redimensionner(image,facteur):
width = int(image.shape[1] * facteur / 100)
height = int(image.shape[0] * facteur / 100)
dim = (width, height)
return cv2.resize(image, dim, interpolation = cv2.INTER_AREA)
Cette fonction redimensionne une image en gardant correct le ratio hauteur / largeur. Elle prend en argument :
Elle renvoie en résultat l'image après redimensionnement.
def mse(img1, img2):
h, w = img1.shape
diff = cv2.subtract(img1, img2)
err = np.sum(diff**2)
mse = err/(float(h*w))
return mse, diff
Cette fonction prend en argument les deux images à comparer. Ces images doivent être aux mêmes dimensions.
Elle renvoie deux résultats : l'erreur moyenne et l'image des différences
def display(image,winName):
if len(image.shape)==2:
# histogramme
fig = plt.figure()
ax = fig.add_subplot(111)
hist = cv2.calcHist([image], [0], None, [256], [0, 256])
ax.plot(hist)
ax.axis("tight")
imgH = plotToImage(fig)
imgH = cv2.resize(imgH,(image.shape[1],int(image.shape[0]/3)), interpolation = cv2.INTER_AREA)
cv2.imshow(winName,np.vstack((cv2.merge((image,image,image)),imgH)))
else:
h = int(image.shape[0]/3)
w = int(image.shape[1]/3)
# composantes
a, b, c = cv2.split(image)
aa = cv2.resize(cv2.merge((a,a,a)),(w,h), interpolation = cv2.INTER_AREA)
bb = cv2.resize(cv2.merge((b,b,b)),(w,h), interpolation = cv2.INTER_AREA)
cc = cv2.resize(cv2.merge((c,c,c)),(w,image.shape[0]-2*h), interpolation = cv2.INTER_AREA)
# histogramme
fig = plt.figure()
ax = fig.add_subplot(111)
for (chan, color) in zip([a,b,c],("b", "g", "r")):
# create a histogram for the current channel and plot it
hist = cv2.calcHist([chan], [0], None, [256], [0, 256])
ax.plot(hist, color=color)
ax.axis("tight")
imgH = plotToImage(fig)
imgH = cv2.resize(imgH,(image.shape[1]+w,h), interpolation = cv2.INTER_AREA)
cv2.imshow(winName,np.vstack((np.hstack((image,np.vstack((aa,bb,cc)))),imgH)))
Cette fonction affiche une fenêtre OpenCV contenant une image, les vignettes des différentes composantes couleur de cette image ainsi que les histogrammes associés. Si c'est une image en niveaux de gris, il n'y aura pas de vignettes de composantes et un seul histogramme sera affiché.