Traitements et analyses d'images
en langage python
L'essentiel de cette page
Traiter une image, c'est d'abord jouer sur les couleurs, la luminosité, le contraste. Dans un deuxième temps, c'est faire de la segmentation et utiliser le machine learning pour détecter des formes, des objets.
1- Ouvrir une image sous python
Installer PIL avec la commande pip :
pip install Pillow
from PIL import Image # Pour la gestion des images
import easygui # Pour ouvrir automatiquement en cliquant dans une fenêtre de l'explorateur (pas besoin d'indiquer un nom ni un chemin)
myimg = Image.open(easygui.fileopenbox())
Voir l'image
myimg.show()
La voir dans une fenêtre graphique
import matplotlib.pyplot as plt
plt.imshow(myimg) ; plt.show()
2- Décrire/retoucher une image en la convertissant en objet numpy
1- Convertir l'image au format numpy et la décrire
import numpy as np
img = np.array(myimg)
plt.imshow(img) ; plt.show() # On peut continuer à utiliser la fonction imshow pour visualiser l'image même au format numpy
print("Dimensions de l'image : ",img.shape) # Dimensions x, y et nombre de couleurs
print("Pixel 500 sur 200 : ",img[200,500])
print("Composante rouge de ce dernier pixel :",img[200,500][0])
# Tous le rouge de tous les pixels
print("Rouge : ",img[:,:,0])
2- Convertir les composantes rouge, vert et bleu en dataframe
red = np.reshape(img[:,:,0],img.shape[0]*img.shape[1])
green = np.reshape(img[:,:,1],img.shape[0]*img.shape[1])
blue = np.reshape(img[:,:,2],img.shape[0]*img.shape[1])
from pandas import *
couleurs = DataFrame({"r":red,"g":green,"b":blue})
print(couleurs.head(10))
3- Réaliser des transformations sur ces composantes (exemples)
couleurs = couleurs + 30 # j'éclaircis tout de 30 niveau sur 255
couleurs.r = round(couleurs.r/3,0) # Je divise par 3 les valeurs de rouge
4- Fabriquer une nouvelle image à partir des composantes rouge, vert bleu retravaillée
img1 = img.copy()
red = np.reshape(np.array(couleurs.r),(img.shape[0],img.shape[1]))
green = np.reshape(np.array(couleurs.g),(img.shape[0],img.shape[1]))
blue = np.reshape(np.array(couleurs.b),(img.shape[0],img.shape[1]))
img1[:,:,0] = red
img1[:,:,1] = green
img1[:,:,2] = blue
5- Afficher comparer l'image avant après
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(img1) ; plt.show()
3- Conversion en niveaux de gris, redimensionnement et facilitation de l'affichage
Une fonction pour faciliter l'affichage en basculant automatiquement en fonction du fait que l'image est en couleur ou en niveau de gris
def display(img,cmap='gray') :
if (len(img.shape) == 3) :
fig, ax = plt.subplots(figsize=(15, 12))
plt.imshow(img) ; plt.show()
else :
fig, ax = plt.subplots(figsize=(15, 12))
plt.imshow(img,cmap=cmap, vmin=0, vmax=255) ; plt.show()
Une fonction pour convertir une image en niveau de gris
###############################
# NVEAU DE GRIS
###############################
def gray_scale_img(img) :
temp_img = img[:,:,1]
temp_img = temp_img.reshape(temp_img.shape[0],temp_img.shape[1])
temp_img[:,:] <- img[:,:,0]*0.2126+img[:,:,1]*0.7152+img[:,:,2]*0.0722
return(temp_img)
img_grey = gray_scale_img(img)
display(img_grey)
Une fonction simple qui va réduire la taille d'une image (attention, pas de lissage = perte de signal plus accrue)
coeff : est le coeff de réduction
def redim_img(img,coeff=3) :
colonnes = list(range(0,img.shape[1]))
colonnes = colonnes[0::coeff]
lignes = list(range(0,img.shape[0]))
lignes = lignes[0::coeff]
if len(img.shape)==3 :
img_temp = img[lignes,:,:]
img_temp = img_temp[:,colonnes,:]
else :
img_temp = img[lignes,:]
img_temp = img_temp[:,colonnes]
return(img_temp)
Liens externes pour redimensionner une image avec un lissage de l'information (éviter les pertes de signal) :
# Réduction de la taille d'un facteur 6
img_red = redim_img(img,6)
display(img_red)
On voit que l'image est plus pixellisée, mais avantage, plus facile à manipuler pour tester des traitements.
4- Convolution, traitement de flou et de contraste
Une fonction pour appliquer un flou médian, moyen ou un calcul d'écart-type (cliquer sur la flèche pour afficher le code)
import numpy as np
def smooth(img,type="mean",kernel=9) : # type : convol, mean, median, sd...
img1 = img.copy()
border = (kernel-1)/2
border = int(border)
if (len(img.shape)==3) :
colored = img.shape[2]
else :
img = img.reshape(img.shape[0],img.shape[1],1)
img1 =img1.reshape(img1.shape[0],img1.shape[1],1)
colored = 1
for color in range(0,colored) :
lignemax = (img.shape[0]-border-1)
lignemax = int(lignemax)
for ligne in range(0,img.shape[0]) :
for colonne in range(0,img.shape[1]) :
lignemin = (ligne-border)
if (lignemin<0) :lignemin=0
lignemax = (ligne+border+1)
if (lignemax>(img.shape[0]-1)) : lignemax = img.shape[0]-1
colmin = (colonne-border)
if colmin < 0 : colmin=0
colmax = (colonne+border+1)
if colmax>(img.shape[1]-1) : colmax = img.shape[1]-1
extrait = img[lignemin:lignemax,colmin:colmax,(color):(color+1)]
if type == "mean" :
new_value = np.mean(extrait)
elif type == "median" :
new_value = np.median(extrait)
elif type == "sd" :
new_value = np.std(extrait, ddof =1)
img1[ligne,colonne,color] = new_value
if (colored==1):
img1 = img1.reshape(img.shape[0],img.shape[1])
return(img1)
img_median = smooth(img_red,"median",9)
display(img_median)
Autres types disponibles : mean (flou moyen) et sd (détection des zones de signal par l'écart-type).
Le 9 ici indique la taille de la fenêtre.
Une fonction pour appliquer un noyau de convolution (code à copier-coller en cliquant sur la flèche)
# Dimensions de l'image
img.shape
def convol(img,kernel) : # type : convol, mean, median, sd...
img1 = img.copy()
border = (kernel.shape[0]-1)/2
border = int(border)
if (len(img.shape)==3) :
colored = img.shape[2]
else :
img = img.reshape(img.shape[0],img.shape[1],1)
img1 =img1.reshape(img1.shape[0],img1.shape[1],1)
colored = 1
for color in range(0,colored) :
lignemax = (img.shape[0]-border-1)
lignemax = int(lignemax)
for ligne in range(border,lignemax) :
for colonne in range(border,(img.shape[1]-border-1)) :
#print(ligne) ; print(colonne) ; print(color)
extrait = img[(ligne-border):(ligne+border+1),(colonne-border):(colonne+border+1),(color):(color+1)]
extrait = extrait.reshape(kernel.shape[0],kernel.shape[0])
new_value = np.sum(extrait*kernel)
img1[ligne,colonne,color] = new_value
if (colored==1):
img1 = img1.reshape(img.shape[0],img.shape[1])
return(img1)
Exemples d'applications :
# Noyau de contours
contour = np.array([-1,-1,-1,-1,8,-1,-1,-1,-1])
contour = contour.reshape(3,3)
print(contour)
# Netteté
net = np.array([0,-1,0,-1,5,-1,0,-1,0])
net = net.reshape(3,3)
print(net)
# Exemple d'application du noyau de netteté
img_net = convol(smooth(redim_img(img_grey)),net)
display(img_net)
Cette fonction est l'équivalent de la fonction de scipy, ndimage.convolve(), mais elle a l'avantage de s'adapter au format couleur ou niveau de gris automatiquement, tout en permettant de contrôler le code pas à pas. Cet équivalent :
from scipy import ndimage
# Travail sur une seule composante couleur de img à la fois
imgA = img[:,:,1]
imgA = imgA.reshape(imgA.shape[0],imgA.shape[1])
img2 = ndimage.convolve(imgA, contour, mode='constant', cval=0.0)
5- Segmenter une image et détecter les régions de même nature
Il existe plusieurs méthode de k-means pour catégoriser automatiquement les points et les segmenter mais problème ! C'est en général tellement lourd que ça plante.
On pourrait très bien faire un k-means classique sur le tableau de composantes rgb de l'exemple ci-dessus (partie 2) mais la fonction de k-means a toutes les chances de planter à cause de la taille de la dataframe.
L'idéal est d'utiliser opencv, ce module permet d'implémenter de nombreuses fonctions très rapide de traitement d'images.
1- Réaliser une segmentation par k-means
1) Défaire l'image pour la transformer dans une array
Z = img_red.reshape((-1,3))
Z = np.float32(Z)
2) Réaliser le K-means sur l'image déroulée
from sklearn.cluster import KMeans
nombre_de_categories = 5
kmeans = KMeans(n_clusters=nombre_de_categories).fit(Z)
groupes = kmeans.predict(Z)
img_groupes = groupes.reshape(img_red.shape[0],img_red.shape[1])
display(img_groupes*40)
Les 5 catégories me permettent d'estimer ici que j'ai isolé mon herbier (couleur sombre) : je vais donc pouvoir continuer en segmentant mon image.
3) Récupérer les valeurs moyennes de couleurs de chaque catégorie
centers = kmeans.cluster_centers_
centers = centers.astype(np.uint8)
4) Attribuer à chaque pixel la valeur moyenne de couleur de sa catégorie (segmentation)
Zimg = Z.copy()
for i in range(0,centers.shape[0]) :
Zimg[np.where(groupes==i),:] = centers[i,:]
# recompiler une image segmentée
img_K = Zimg.reshape(img_red.shape[0],img_red.shape[1],img_red.shape[2])
img_K = img_K.astype(np.uint8)
display(img_K)
On voit sur l'image 5 couleurs. Clairement, les herbiers correspondent au pixels sombre. Il faut voir quel numéro de catégorie lui correspond.
5) Afficher les couleurs de centres pour identifier la catégorie d'intérêt
centers_img = centers.reshape(1,nombre_de_categories,3)
display(centers_img)
On voit que le groupe 0 correspond à nos herbiers de posidonies.
6) Binariser l'image avec 1) ce que je veux (herbiers) et 0) ce que je ne veux pas (le reste).
herbiers = groupes.copy()
mycat = 0
herbiers[np.where(herbiers!=mycat)]= 1000
herbiers[np.where(herbiers==mycat)]= 1
herbiers[np.where(herbiers==1000)]= 0
plt.hist(herbiers) ; plt.show()
herbiers2 = herbiers.copy()
herbiers2 = herbiers2.reshape(img_red.shape[0],img_red.shape[1])
display(herbiers2*250)
Tous les pixels herbiers valent maintenant 1 et les autres pixels 0. Je vais maintenant pouvoir tenir compte des dimensions spatiales pour localiser les herbiers. La méthode SVM permet cette approche.
2- Réaliser une détection lissée des zones par SVM
1) Créons un tableau qui contient 3 colonnes, 2 qui détaillent les position des pixels (ligne et colonne) et la troisième le type (1- herbier ou 0- pas herbier)
# Créons une array contenant une colonne row, une colonne col, une colonne catégorie (herbier)
myrows = herbiers.copy()
myrows = myrows.reshape(herbiers2.shape[0],herbiers2.shape[1])
for i in range(0,myrows.shape[0]) :
myrows[i,:] = i
mycols = herbiers.copy()
mycols = mycols.reshape(herbiers2.shape[0],herbiers2.shape[1])
for i in range(0,mycols.shape[1]) :
mycols[:,i] = i
myrows = myrows.reshape((-1))
mycols = mycols.reshape((-1))
print(myrows.shape)
print(mycols.shape)
print(herbiers.shape)
DOC = np.empty([herbiers.shape[0],3])
DOC.shape
DOC[:,0] = myrows
DOC[:,1] = mycols
DOC[:,2] = herbiers
#DOC[0:200,:]
plt.scatter(DOC[:,0],DOC[:,1],c=DOC[:,2])
plt.show()
2) Appliquons la méthode SVM pour entourer les zones à herbiers (paramétrage gamma et C à ajuster à son image)
# Regroupement SVM
from sklearn import svm
clf = svm.SVC(kernel='rbf', C=0.1, gamma=0.1)
# gamma : épaisseur du feutre, grossier ou petits traits
# C : inverse du coût => coefficient de lissage
clf.fit(DOC[:,0:2],DOC[:,2])
prediction = clf.predict(DOC[:,0:2])
prediction_img = prediction.reshape(img_red.shape[0],img_red.shape[1])
prediction_img = prediction_img.astype(np.uint8)
prediction_img.shape
display(prediction_img*250)
On voit que les herbiers sont pas si mal identifiés. Le bateau au centre a pu être éliminé.
Ci-dessous, le résultat de la détection est superposé en rouge à l'image. Détection d'herbiers en cours ! ;-)
# Pour afficher le résultat de détection coloré en rouge
compil = img_red.copy()
# Ajoute 60 à la composante rouge de chaque pixel de la catégorie 1
compil[:,:,0] = compil[:,:,0] + prediction_img*60
display(compil)