Classification de bonbons avec TensorFlow Essais de classification de bonbons par Intelligence Artificielle avec OpenCV et TensorFlow/Keras. Note" open Je ne suis pas un spécialiste de l'IA, seulement un utilisateur curieux. Cet article est un résumé de mes premiers essais avec Tensorflow. Les méthodes développées sont "largement" perfectibles mais peuvent donner des pistes à tout personne souhaitant se lancer avec Tensorflow. Objectifs Faire de la classification d’images pour déterminer la nature de la confiserie Pour entraîner le modèle, il est nécessaire d’avoir suffisamment d’images de chaque confiserie à identifier. On effectuera de l’apprentissage supervisé, le label de chaque image utilisée pour l’entraînement sera connu. Il y aura 4 Labels : Bounty , Maltesers, Snickers, Twix Pour chaque confiserie: 660 images sont prises dont 250 en condition de lumière dégradée Ce qui fait 660 x 4 = 2640 images -> Il faut faire un programme permettant d’automatiser les prises de vue, sinon, on va y passer la journée. Pour rendre les images exploitables pour un réseau de neurone avec une puissance de PC classique, il faut limiter la résolution à 250 pixels x 250. Le programme devra effectuer un « découpage » (Crop) pour extraire la zone utile d’image avec un format 250 x 250, car de base, la WebCam fait une capture en 640x480. Installation expérimentale Une zone de travail de 12cm sur 12 cm est dessinée sur la feuille. Avec le crop à 250 x 250 pixels, on ne prendra comme image utile ce qui se trouve à l’intérieur du carré. Logiciels Ubuntu 22.04 LTS installée sur une VirtualBox VisualStudioCode pour Coder en Python (ou autre éditeur au choix) Il faut installer pip3 pour installer facilement les libs python On installe tensorflow, numpy, opencv, matplotlib avec la commande pip3 install Script pour l'acquisition des Datasets d'images Le script python permettant la capture d'images pour constituer le DataSet d'images. Quand on appuie sur espace : une image est prise Le crop en 250 x 250 est fait (position Y1:Y2 et X1:X2 des pixels pour la base du crop et le point d'arrivé) Cela enregistre l’image On incrémente le compteur d’image Quand on appuie sur Echap : On arrête la webcam On détruit la fenêtre de capture import cv2 cam = cv2.VideoCapture(0) cv2.namedWindow("test") img_counter = 0 while True: ret, frame = cam.read() if not ret: print("failed to grab frame") break cv2.imshow("test", frame) k = cv2.waitKey(1) if k%256 == 27: # ESC pressed print("Escape hit, closing...") break elif k%256 == 32: # SPACE pressed img_name = "Maltesers_{}.png".format(img_counter) # Cropping an image Y1:Y2 , X1:X2 cropped_image = frame[115:365, 140:390] cv2.imwrite(img_name, cropped_image) print("{} written!".format(img_name)) img_counter += 1 cam.release() cv2.destroyAllWindows() Pour exécuter le script de capture: utiliser la commande python3 test_Capture_Maltesers.py Il faudra modifier le script python de capture pour avoir un nommage cohérent des images. Grâce à cette méthode, 2 secondes suffisent pour capturer une image, soit tout de même une bonne 1h pour générer les 2640 images de DataSets. Script Python pour générer le modèle avec TensorFlow+Keras Le script utilisé pour générer le modèle est inspiré du turoriel TensorFlow pour la classification d'images : Tuto_classification_images L'adaptation de ce script à notre problématique de classification de bonbons est faite ici (cliquer pour ouvrir le code) import numpy as np import os import PIL import PIL.Image import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.models import Sequential import pathlib import matplotlib.pyplot as plt data_dir = pathlib.Path('/home/philippe/Documents/DataSetsBonbons') print(data_dir) image_count = len(list(data_dir.glob('*/*.png'))) print(image_count) print(tf.__version__) batch_size = 660 img_height = 250 img_width = 250 train_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset="training", seed=123, image_size=(img_height, img_width), batch_size=batch_size) val_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset="validation", seed=123, image_size=(img_height, img_width), batch_size=batch_size) class_names = train_ds.class_names print(class_names) AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE) val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE) normalization_layer = layers.Rescaling(1./255) normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y)) image_batch, labels_batch = next(iter(normalized_ds)) first_image = image_batch[0] # Notice the pixel values are now in `[0,1]`. print(np.min(first_image), np.max(first_image)) num_classes = len(class_names) model = Sequential([ layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)), layers.Conv2D(16, 3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(32, 3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(64, 3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Flatten(), layers.Dense(128, activation='relu'), layers.Dense(num_classes) ]) model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy']) model.summary() epochs=30 history = model.fit( train_ds, validation_data=val_ds, epochs=epochs ) acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs_range = range(epochs) plt.figure(figsize=(8, 8)) plt.subplot(1, 2, 1) plt.plot(epochs_range, acc, label='Training Accuracy') plt.plot(epochs_range, val_acc, label='Validation Accuracy') plt.legend(loc='lower right') plt.title('Training and Validation Accuracy') plt.subplot(1, 2, 2) plt.plot(epochs_range, loss, label='Training Loss') plt.plot(epochs_range, val_loss, label='Validation Loss') plt.legend(loc='upper right') plt.title('Training and Validation Loss') plt.show() model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy']) model.summary() model.save('saved_model/my_model.h5') Ce script enregistre le modèle généré dans un répertoire "saved_model" sous le nom my_model.h5 Le nombre d’époch est augmenté à 30 pour avoir une bonne convergence la précision d’entraînement et celle de validation. On remarque une bonne convergence entre l'entraînement et la validation Script pour tester le modèle généré Le script pour tester le modèle généré est disponible ici : import numpy as np import os import PIL import PIL.Image import tensorflow as tf import sys import cv2 from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.models import Sequential cam = cv2.VideoCapture(0) cv2.namedWindow("test") new_model = tf.keras.models.load_model('/home/philippe/Documents/Test_TF_on_new_model/my_model.h5') class_names=['Bounty','Maltesers', 'Snickers','Twix'] #new_model.summary() img_height = 250 img_width = 250 while True: ret, frame = cam.read() if not ret: print("failed to grab frame") break cv2.imshow("test", frame) k = cv2.waitKey(1) if k%256 == 27: # ESC pressed print("Escape hit, closing...") break elif k%256 == 32: # SPACE pressed # Cropping an image Y1:Y2 , X1:X2 cropped_image = frame[115:365, 140:390] cv2.imwrite("test.png", cropped_image) print("written!") img = tf.keras.utils.load_img('/home/philippe/Documents/Test_TF_on_new_model/test.png',target_size=(img_height, img_width)) img_array = tf.keras.utils.img_to_array(img) img_array = tf.expand_dims(img_array, 0) # Create a batch predictions = new_model.predict(img_array) score = tf.nn.softmax(predictions[0]) print("This image most likely belongs to {} with a {:.2f} percent confidence.".format(class_names[np.argmax(score)], 100 * np.max(score))) cv2.waitKey(1) cam.release() cv2.destroyAllWindows() (Il faudra adapter ce script à votre environnement) Fonctionnement du script: On lance le script avec python3 test_du_model.py Une fenêtre WebCam s'ouvre Un appui sur Espace permet de capturer la confiserie L'analyse se fait Le résultat de détection est affiché avec un indice de confiance Le cycle se répète pour chaque appui sur la touche espace Un appui sur la touche Echap permet de quitter le script Le modèle répond juste pour des conditions d'éclairage comprises entre normales et dégradées (réduction de l'alimentation des LEDs d'éclairage de 12V à 8V) : Toutes les confiseries sont reconnues avec un indice de confiance compris entre 88.9% et 100% Essais du modèle en mode TRÈS dégradé On est dans un cas extrême, quand il n’y a plus d’éclairage, tous les bonbons sont des Snickers à 100% (ce qui est étrange, l’indice de confiance aurait du être plus faible…) Bilan Les outils IA sont aujourd’hui suffisamment accessibles pour qu'un débutant puisse générer un modèle qui semble robuste. Les lignes de code Python sont très simples et se limitent à des appels de fonctions, aucune algorithmie complexe ne fut programmée. Il faut un PC avec 16 Go de RAM est confortable pour calculer le modèle, une fois le modèle calculé plus besoin d’autant de RAM. Le modèle semble étonnamment robuste, surtout dans des bonnes conditions d’éclairage.