Tensorflow pour classification de bonbons
Classification de bonbons avec TensorFlow
Essais de classification de bonbons par Intelligence Artificielle avec OpenCV et TensorFlow/Keras.
Objectifs
Faire de la classification d’images pour déterminer la nature de la confiserie
Bounty | Maltesers | Snickers | Twix |
---|---|---|---|
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 figure 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 figure 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 figure 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 figure, 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.figure
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.figure
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 figure 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 figure 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.