Classification d’objets avec GluonCV

Cet article est le numéro 2 de 2 articles de la série Computer Vision

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())
Balle de basket
# 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 !!! 🎉 🤗 🎊

exemples d'images prédites
4 images prédites : 2 balles de basket en haut, 1 balle de foot en bas à gauche et 1 balle de tennis en dernier

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.

Series Navigation<< Détection d’Objets avec la librairie GluonCV
Laisser un commentaire

Votre adresse email ne sera pas publiée.

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

Voulez-vous en savoir plus sur la Data Science ?

Inscrivez-vous alors à notre newsletter et vous receverez gratuitement nos derniers articles et actualités ! 
S'INSCRIRE MAINTENANT 
close-link