Le Deep Learning a permis une avancée notable dans plusieurs domaines de recherche dont le Computer Vision (Vision par Ordinateur in french 😄). Dans cet article, pour poursuivre la série, je vous présente l’une des applications du Computer Vision : la classification d’objets avec la librairie Python GluonCV.
Pourquoi classer des objets ?
La tâche de classification d’objets est l’une des bases du Computer Vision tout comme la détection et la segmentation sémantique.
En considérant l’application des voitures autonomes, on voit clairement l’importance de la classification d’objets. En effet, une voiture autonome doit être capable de faire la différence entre les véhicules de tout type pendant la circulation, mais aussi d’identifier les panneaux de circulation, les bandes blanches, les piétons, les feux tricolores, etc.
Prenons un autre exemple, nous avons une base d’images satellitaires et nous voulons identifier les zones boisées, les zones aquatiques et les déserts. Grâce à la classification par Computer Vision, il est possible de pouvoir le faire sans la présence d’un géographe.
Avec ces deux exemples, l’on répond aisément à la question. Et grâce à la librairie GluonCV, nous pouvons faire de la classification d’objets en utilisant l’un de ces modèles Zoo : MobileNet qui présente un bon rapport précision (accuracy) / mémoire utile.
Un petit TP 🤓
Tout comme pour la détection d’objets, nous allons procéder d’abord à la configuration de notre environnement Python.
# Modules pour la modélisation
from mxnet import image, nd
from gluoncv import model_zoo, data
# Module pour la visualization
import matplotlib.pyplot as plt
Modélisation et traitement d’images
Ensuite, nous chargeons notre modèle de classification ; on optera pour l’option de pré-entraînement.
# Chargement du modèle MobileNet pré-entraîné
network = model_zoo.mobilenet1_0(pretrained=True)
Passons maintenant au traitement de l’image.
Notre modèle prend en entrée une matrice de dimension 4 qui est censé représentée notre image à classer. À la différence de la tâche de détection, nous utiliserons au lieu de yolo, une transformation basée sur ImageNet.
def load_image(filepath):
"""
Charge une image.
Parameter
----------
filepath: str
Chemin d'accès au fichier de notre image RGB de format JPG.
Returns
--------
imageHWC: nd.NDArray
Tableau des intensités de pixel de la forme HWC (H: Height ; W: Width ; C: Channel)
"""
imageHWC = image.imread(filepath)
return imageHWC
def transform_image(array):
"""
Transforme une image en :
1) Redéfinissant la taille de l'image à la plus petite dimension qui est 224.
Exemple : (448, 1792) -> (224, 896).
2) Recadrant à un carré central de dimension (224, 224).
3) Convertissant l'image de la forme HWC à CHW.
4) Normalisant l'image en utilisant des statistiques de ImageNet (normalisation
par moyenne et variance du canal couleur).
5) Créant un batch d'une seule image.
Parameter
----------
array: nd.NDArray dans la forme HWC
Returns
--------
transformed_image: nd.NDArray
Batch d'une image transformée (dans la forme NCHW).
"""
transformed_image = data.transforms.presets.imagenet.transform_eval(array)
return transformed_image
# Chargeons une image
imageHWC = load_image("./sample_data/basketball0.jpg")
# Affichons l'image
plt.imshow(imageHWC.asnumpy())

# Test de la fonction de transformation
transformed_image = transform_image(imageHWC)
transformed_image.shape
[Output]
(1, 3, 224, 224)
C’est bien beau tout ça mais comment trouver les classes ?
On voit cela maintenant à l’aide d’une fonction 🤠
def find_class_idx(label):
"""
Retourne l'index de la classe d'un terme particulier.
Parameter
----------
label: str
Terme de la classe.
Returns
--------
class_index: int
Index de la classe.
"""
for index in range(len(network.classes)) :
if network.classes[index] == label :
class_index = index
return class_index
# Affichons l'index de quelques mots
print(find_class_idx("basketball"))
print(find_class_idx("football"))
print(find_class_idx("tennis ball"))
[Output]
430
None
852
Cette fonction nous permet à partir d’un mot (label) trouvé son index parmi les 1000 index de ImageNet. Comme vous pouvez le voir, tous les mots sont en anglais et certains seulement sont reconnus.
Afin de prédire notre image parmi les 1000 classes existantes, nous utiliserons une matrice de probabilités grâce à la fonction softmax
de mxnet.
def predict_probabilities(network, data):
"""
Retourne les probabilités prédites des classes ImageNet pour l'image donnée.
Parameters
-----------
network: mxnet.gluon.Block
Modèle de classification d'images pré-entrainé.
data: mx.nd.NDArray
Batch d'une image transformée de dimension (1, 3, 224, 224).
Returns
--------
probability: nd.NDArray
Tableau des probabilités de dimension (1000,).
"""
prediction = network(data)
prediction = prediction[0]
probability = nd.softmax(prediction)
probability = nd.round(probability*100)/100
return probability
# Dimension de notre matrice
pred_probas = predict_probabilities(network, transformed_image)
pred_probas.shape
[Output]
(1000,)
Nous allons pour la suite écrire un classifieur de balle de basket 🏀 en utilisant notre matrice de probabilités pour la prédiction.
Classifieur de balle de basket 🏀
Il y a pas mal de petites fonctions dans ce TP 🧐
Et on n’en a pas encore fini, enfin presque….
Maintenant que nous savons trouver l’index d’une classe et calculer la probabilité d’une image, on va calculer la probabilité que telle image correspond à notre classe basketball
.
C’est le préalable pour notre classifieur.
def slice_basketball_class(pred_probas):
"""
Extrait les probabilités associées à la classe basketball.
Parameter
----------
pred_probas: nd.NDArray
Matrice des probabilités d'ImageNetde dimension (1000,).
Returns
--------
class_probability: numpy.float32
Probabilité de la classe basketball.
"""
class_index = find_class_idx("basketball")
class_probability = pred_probas[class_index].asscalar()
return class_probability
Enfin, on peut construire notre classifieur et classer nos images présentes ici.
class BasketBallClassifier():
def __init__(self, network):
self._network = network
def classify(self, filepath):
# Chargement de l'image
imageHWC = load_image(filepath)
# Transformation de l'image
transformed_image = transform_image(imageHWC)
# Matrice de probabilités
pred_probas = predict_probabilities(self._network, transformed_image)
pred_proba = slice_basketball_class(pred_probas)
# Affichage de l'image
self._visualize(transformed_image, pred_proba)
def _visualize(self, transformed_image, pred_proba):
"""
Puisque transformed_image est en disposition NCHW et que les valeurs sont normalisées,
cette méthode découpe et transpose pour donner CHW comme requis par matplotlib,
et met à l'échelle (-2, +2) à (0, 255) linéairement.
"""
chw_image = transformed_image[0].transpose((1,2,0))
chw_image = ((chw_image * 64) + 128).clip(0, 255).astype('uint8')
plt.imshow(chw_image.asnumpy())
plt.title('{0:.2%} de confiance que cette image est une balle de basket.'.format(pred_proba), y=1.05)
Application
Après avoir initialisé notre classifieur, on fait pour chaque la prédiction.
# Initialisation
classifier = BasketBallClassifier(network)
# Prédiction
classifier.classify("./sample_data/image_à_prédire.jpg")
Tada !!! 🎉 🤗 🎊

Conclusion
Durant cet article, nous avons énoncé quelques applications de la tâche de classification de Computer Vision dont la détection. À l’aide de cet TP, on a ensemble créé un classifieur simple grâce à MobileNet de GluonCV.
N’hésitez à laisser des commentaires et à vous abonner pour avoir la suite de cette série sur le Computer Vision.