Cet article est un tutoriel sur comment utiliser le modèle pré-entrainé de Tensorflow MobileNet. Ce modèle permet de faire de la détection et de la localisation d’objets. Plus précisément à partir d’une image comme ci-dessous,
il détecte les différents objets présents et dessine des boîtes autour. L’image suivante montre le résultat de la détection d’objet :
Dans ce tutoriel, on supposera que le lecteur a déjà installé la librairie Python Tensorflow 2.
Quelques mots sur le modèle MobileNet de Tensorflow
Premièrement c’est un modèle qui utilise l’architecture introduite dans cet article pour faire de la détection d’objet. Deuxièment il a été pré-entraîné sur le jeu de donnés COCO (abréviation de « Common Objects in Context »). C’est un jeu qui contient plus de 2 millions d’images avec 80 types d’objets dans leur environnement naturelle.
Préparation de l’environnement
Pour pouvoir utiliser le modèle, il faut d’abord télécharger des fichiers du projet « tensorflow/models ».
Installation du dépôt « tensorflow/models »
Il faut commencer par installer le dépôt Git github.com/tensorflow/models qui contient plusieurs scripts Python utiles. Vous pouvez par exemple télécharger le dépôt via cet URL github.com/tensorflow/models/archive/master.zip Ensuite le fichier zip devra être extrait dans dossier que j’appellerai « ./models ».
Après que les fichiers soient extraits dans le dossier « ./models », il faut s’assurer que Python puisse trouver le sous-dossier « ./models/research ».
Vous pouvez soit ajouter ce dossier dans la variable d’environnement PYTHONPATH.
Ou bien utiliser la méthode qui consiste à mettre dans le script Python la commande sys.
1 2 3 4 | import sys PATH_TO_TENSORFLOW_MODELS = ## Indiquer ici le chemin du dossier "./models" sys.append(PATH_TO_TENSORFLOW_MODELS + "/research") |
Compiler les fichiers .proto
Ensuite il y a une étape un peu délicate. Elle consiste à générer des script Python avec Protocol Buffers.
Dans le dossier projet « ./models/
La procédure est la suivante :
- D’abord Suivez le lien ici pour télécharger l’archive correspodant votre système d’exploitation.
- Ensuite décompresser l’archive et repérer le chemin du fichier exécutable « protoc » qui se trouve dans le dossier « /bin » de l’archive.
- Puis ouvrez un terminale de commande et naviguer dans le dossier « ./models/research ».
- Enfin exécuter la commande PATH/bin/protoc –python_out=. object_detection/protos/string_int_label_map.proto.
Quelques remarques sur cette procédure.
D’abord la dernière commande PATH doit être le chemin où vous avez décompresser l’archive.
Ainsi PATH/bin/protoc est le chemin de l’exécutable protoc.
Ensuite la directive –python_out=. object_detection/
Si tout c’est bien passé, vous devriez voir apparaître le fichier « string_int_label_map_pb2.py » dans le dossier « ./models/research/object_detection/protos ».
Note : si vous souhaitez utiliser tous les outils du projet « ./models/
1 2 3 4 5 6 7 | import sys import os for filename in os.listdir('./object_detection/protos'): ## parcourt des fichiers de ./object_detection/protos if filename.endswith('.proto'): ## Si le nom du fichier se termine par .proto code = os.system("protoc --python_out=. object_detection/protos/" + filename) # Exécuter la commande protoc print(filename, code) ## vérifer que code vaut bien 0 pour s'assurer que tout c'est bien passé |
Codage du script
Chargement du modèle MobileNet
Le modèle peut être télécharger ici ici. Une fois que vous avez décompressé l’archive dans un dossier que j’apellerai ici « ./ssd_mobilenet », repérez le sous dossier « ./ssd_mobilenet/saved_model » qui contient le fichier « saved_model.pb ».
Pour charger ce modèle sous Python, il faut utiliser la fonction de Tensorflow tensorflow.
1 2 3 4 5 6 7 8 9 10 | import tensorflow def load_ssd_mobilenet(): PATH_TO_SSD_MOBILENET_ = "/home/auguste/Documents/WebSite/html/ssd_mobilenet_v1_coco_2017_11_17" model_data = tensorflow.saved_model.load(PATH_TO_SSD_MOBILENET_ + "/saved_model") model = model_data.signatures['serving_default'] return model MobileNet = load_ssd_mobilenet() |
Chargement de l’étiquetage
Le modèle que l’on a chargé ne renvoie pas le nom des objets qu’il détecte, mais seulement un numéro. Il faut alors charger un Label Map qui est un dictionnaire associant à chaque numéro un nom d’objet.
Ce Label Map se trouve dans « ./models/
1 2 3 4 5 6 7 8 9 | import object_detection.utils.label_map_util ## Note : object_detection se trouve dans "./models/research" def load_label_map(): PATH_TO_LABELS = PATH_TO_TENSORFLOW_MODELS + "/research/object_detection/data/mscoco_label_map.pbtxt" category_index = object_detection.utils.label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True) return category_index Label_Map = load_label_map() |
Il n’est pas necessaire de connaître la structure de ce dictionnaire, mais pour information voilà à quoi il ressemble :
1 | print(Label_Map) |
1: {'id': 1, 'name': 'person'},
2: {'id': 2, 'name': 'bicycle'},
3: {'id': 3, 'name': 'car'},
4: {'id': 4, 'name': 'motorcycle'},
5: {'id': 5, 'name': 'airplane'},
6: {'id': 6, 'name': 'bus'},
7: {'id': 7, 'name': 'train'},
8: {'id': 8, 'name': 'truck'},
9: {'id': 9, 'name': 'boat'},
10: {'id': 10, 'name': 'traffic light'},
11: {'id': 11, 'name': 'fire hydrant'},
13: {'id': 13, 'name': 'stop sign'},
14: {'id': 14, 'name': 'parking meter'},
15: {'id': 15, 'name': 'bench'},
16: {'id': 16, 'name': 'bird'},
17: {'id': 17, 'name': 'cat'},
18: {'id': 18, 'name': 'dog'},
19: {'id': 19, 'name': 'horse'},
20: {'id': 20, 'name': 'sheep'},
21: {'id': 21, 'name': 'cow'},
22: {'id': 22, 'name': 'elephant'},
23: {'id': 23, 'name': 'bear'},
24: {'id': 24, 'name': 'zebra'},
25: {'id': 25, 'name': 'giraffe'},
27: {'id': 27, 'name': 'backpack'},
28: {'id': 28, 'name': 'umbrella'},
31: {'id': 31, 'name': 'handbag'},
32: {'id': 32, 'name': 'tie'},
33: {'id': 33, 'name': 'suitcase'},
34: {'id': 34, 'name': 'frisbee'},
35: {'id': 35, 'name': 'skis'},
36: {'id': 36, 'name': 'snowboard'},
37: {'id': 37, 'name': 'sports ball'},
38: {'id': 38, 'name': 'kite'},
39: {'id': 39, 'name': 'baseball bat'},
40: {'id': 40, 'name': 'baseball glove'},
41: {'id': 41, 'name': 'skateboard'},
42: {'id': 42, 'name': 'surfboard'},
43: {'id': 43, 'name': 'tennis racket'},
44: {'id': 44, 'name': 'bottle'},
46: {'id': 46, 'name': 'wine glass'},
47: {'id': 47, 'name': 'cup'},
48: {'id': 48, 'name': 'fork'},
49: {'id': 49, 'name': 'knife'},
50: {'id': 50, 'name': 'spoon'},
51: {'id': 51, 'name': 'bowl'},
52: {'id': 52, 'name': 'banana'},
53: {'id': 53, 'name': 'apple'},
54: {'id': 54, 'name': 'sandwich'},
55: {'id': 55, 'name': 'orange'},
56: {'id': 56, 'name': 'broccoli'},
57: {'id': 57, 'name': 'carrot'},
58: {'id': 58, 'name': 'hot dog'},
59: {'id': 59, 'name': 'pizza'},
60: {'id': 60, 'name': 'donut'},
61: {'id': 61, 'name': 'cake'},
62: {'id': 62, 'name': 'chair'},
63: {'id': 63, 'name': 'couch'},
64: {'id': 64, 'name': 'potted plant'},
65: {'id': 65, 'name': 'bed'},
67: {'id': 67, 'name': 'dining table'},
70: {'id': 70, 'name': 'toilet'},
72: {'id': 72, 'name': 'tv'},
73: {'id': 73, 'name': 'laptop'},
74: {'id': 74, 'name': 'mouse'},
75: {'id': 75, 'name': 'remote'},
76: {'id': 76, 'name': 'keyboard'},
77: {'id': 77, 'name': 'cell phone'},
78: {'id': 78, 'name': 'microwave'},
79: {'id': 79, 'name': 'oven'},
80: {'id': 80, 'name': 'toaster'},
81: {'id': 81, 'name': 'sink'},
82: {'id': 82, 'name': 'refrigerator'},
84: {'id': 84, 'name': 'book'},
85: {'id': 85, 'name': 'clock'},
86: {'id': 86, 'name': 'vase'},
87: {'id': 87, 'name': 'scissors'},
88: {'id': 88, 'name': 'teddy bear'},
89: {'id': 89, 'name': 'hair drier'},
90: {'id': 90, 'name': 'toothbrush'}}
Inférence par le modèle
Lorsqu’on fait une inférence sur un image, le modèle MobileNet renvoie :
- Une liste de boîtes.
-
Chaque boîte est associé :
- Un numéro de classe correspondant au nom d’un objet.
- Un score entre 0 et 1 correspondant à la confiance du numéro de classe.
- Les coordonnées relatifs de la boîte au sein de l’image.
Pour faire un inférence avec le modèle MobileNet, il suffit simplement de l’appeler sur un tenseur de type Tensorflow de format [batch_size, length, width, channel] où channel est de dimension 3. Comme tous les modèles il faut tenir compte de batch_size, ainsi si on veut faire une inférence sur une seule image il faut prendre batch_size de dimension 1. Ensuite en sortie on reçoit un dictionnaire qui est composé des éléments suivants :
- ‘num_detections’ : tenseur de format [batch_size] où chaque entré est le nombre de boîtes. Ici tous les entrées valent toujours 100 (note : si l’image contient moins de 100 objets, alors les boîtes en trop auront un score proche de 0).
- ‘detection_classes’ : tenseur de format [batch_size, N] où N est la valeur commune de ‘num_detections’. La valeur d’entrée $(b,n)$ correspond au numéro de classe de la boîte numéro $n$ sur l’image numéro $b$.
- ‘detection_scores’ : tenseur de format [batch_size, N] où N est la valeur commune de ‘num_detections’. La valeur d’entrée $(b,n)$ correspond au score de la classe (donnée par ‘detection_classes’) de la boîte numéro $n$ sur l’image numéro $b$.
- ‘detection_boxes’ : tenseur de format [batch_size, N, 4] où N est la valeur commune de ‘num_detections’. Les 4 valeurs d’entrée $(b,n)$ correspond au coordonnée de la de la boîte numéro $n$ sur l’image numéro $b$ sous le format $y_{\mathrm_{min}}$, $x_{\mathrm_{min}}$, $y_{\mathrm_{max}}$, $x_{\mathrm_{max}}$ (où $x$ représente l’axe d’indice 0 et $y$ l’axe d’indice 1 de l’image, c-à-d $x$= »axe verticale vers le bas » et $y$= »axe horizontale vers la gauche »).
Pour tracer les boîtes sur l’image, on pourrait le faire à grâce à la main à partir du résultat de l’inféreur,
mais heureusement la fonction a déjà été implémentée à travers
object_detection.
- image : numpy.array de format [height, width, channel] avec channel de dimension 3. Image sur laquelle tracer les boîtes. Attention la fonction trace les boîtes en modifiant ce numpy.array.
- ‘boxes’ : numpy.array de format [N, 4]. liste des coordonnées des boîtes à tracer.
- ‘classes’ : numpy.array de format [N]. liste du numéro de la classe de chaque boîte.
- ‘scores’ : numpy.array de format [N]. liste des numéro de la classe de chaque boîte.
- ‘category_index’ : dictionnaire entre numéro et nom de la classe. Ici c’est le dictionnaire ‘Label_Map’ que l’on a chargé.
- use_normalized_coordinates : booléen (False par défaut). Indique si les coordonnées des boîtes sont normalisés. Pour SSD-MobileNet se sera True
- ‘min_score_thresh’ : nombre entre 0 et 1 (0.5 par défaut). Seules les boîtes de score supérieur à ce nombre seront tracées.
En résumé la fonction suivante permet de faire une inférence sur une image avec MobileNet. Elle renvoie output_dict qui contient le résultat de l’inférence par MobileNet et output_image qui est l’image sur laquelle sont tracés les boîtes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import numpy import object_detection.utils.visualization_utils ## Note : object_detection se trouve dans "./models/research" def predict_mobile_net(image): """ image : numpy.array de format [length, width, channel] avec channels de dimension 3 """ image_exp = numpy.expand_dims(image, axis=0) ## Ajout de la dimension batch_size input_tensor = tensorflow.convert_to_tensor(image_exp) ## Conversion en tensorflow.tensor output_dict = MobileNet(input_tensor) ## Inference ## Elimination de la dimension batch_size et conversion en numpy.array output_dict['num_detections'] = output_dict['num_detections'][0] output_dict['detection_boxes'] = numpy.array(output_dict['detection_boxes'][0]) output_dict['detection_scores'] = numpy.array(output_dict['detection_scores'][0]) output_dict['detection_classes'] = numpy.array(output_dict['detection_classes'][0]).astype('int32') ## Image output_image = image.copy() object_detection.utils.visualization_utils.visualize_boxes_and_labels_on_image_array( image = output_image, boxes = output_dict['detection_boxes'], classes = output_dict['detection_classes'], scores = output_dict['detection_scores'], category_index = category_index, use_normalized_coordinates=True ) return output_dict, output_image |
Voici un exemple d’utiisation de cette fonction.
1 2 3 4 5 6 7 8 9 10 11 | import PIL import matplotlib.pyplot as plt image = numpy.array(PIL.Image.open("/path/image.jpeg")) ## Indiquer le chemin de l'image output_dict, output_image = predict_mobile_net(image) ## Inference ## Affichage de image et de output_image fig, axes = plt.subplots(1,2) axes[0].imshow(image) axes[1].imshow(output_image) fig.show() |
Enfin ceci achève ce tutoriel sur comment utiliser le modèle pré-entraîné MobileNet.