Accéder au contenu principal

Reconstruire des images d’empreintes digitales avec le deep learning (autoencodeur convolutionnel)

Dans ce tutoriel, vous apprendrez à lire des images d’empreintes au format JPEG et à les reconstruire à l’aide d’un autoencodeur convolutionnel.
Actualisé 19 sept. 2026  · 10 min lire

Explorer avec l’IA

ChatGPTClaudePerplexity

Vous utiliserez le jeu de données d’empreintes FVC2002 pour entraîner votre réseau. Pour évaluer l’efficacité de votre modèle, vous le testerez sur deux jeux de données issus de capteurs d’empreintes différents, à savoir les capteurs Secugen et Lumidigm.

Remarque : ce tutoriel se concentre principalement sur l’implémentation pratique des autoencodeurs convolutionnels. Si vous n’êtes pas encore à l’aise avec les réseaux de neurones convolutionnels (CNN) et les autoencodeurs, nous vous conseillons de consulter les tutoriels CNN et Autoencoder.

En résumé, voici les sujets que vous aborderez aujourd’hui :

  • Vous commencerez par comprendre le jeu de données d’empreintes : types d’images, lecture des fichiers, création d’un tableau d’images, exploration, puis prétraitement pour pouvoir les injecter dans le modèle.
  • Concernant l’implémentation de l’autoencodeur convolutionnel : vous allez entraîner le modèle sur les données prétraitées, visualiser les courbes de perte d’entraînement et de validation, puis prédire sur l’ensemble de test.
  • Enfin, vous testerez la robustesse de votre modèle pré-entraîné sur deux jeux de données différents : Secugen et Lumidigm.

Comprendre le jeu de données d’empreintes

Avant de charger les données, il est utile de voir précisément avec quoi vous allez travailler. Le jeu de données FVC2002 est issu de la Fingerprint Verification Competition, organisée en 2000 puis en 2002. Il comprend des empreintes provenant de quatre capteurs différents : capteur optique à bas coût, capteur capacitif à bas coût, capteur optique et générateur synthétique, chacun ayant des dimensions d’image variables. L’ensemble A contient 3200 images, soit 800 par capteur. Vous pourrez le vérifier après le chargement des données ! ;)

Le jeu de données d’empreintes n’est pas prédéfini dans Keras ou TensorFlow. Vous devez donc le télécharger depuis cette source. Voyons cela dans la section suivante.

Remarque : avant de commencer, notez que le modèle est entraîné sur une machine équipée d’un GPU Nvidia 1080 Ti, processeur Xeon e5 GeForce et 32 Go de RAM. Si vous utilisez Jupyter Notebook, vous devrez ajouter trois lignes de code supplémentaires pour spécifier l’ordre des périphériques CUDA et les GPU visibles via le module os.

Dans le code ci-dessous, vous définissez des variables d’environnement dans le notebook avec os.environ. Il est recommandé de le faire avant d’initialiser Keras pour limiter l’utilisation à un seul GPU par le backend TensorFlow. Si la machine sur laquelle vous entraînez possède un GPU en position 0, utilisez 0 au lieu de 1. Vous pouvez le vérifier en exécutant une simple commande dans votre terminal, par exemple nvidia-smi.

import os
os.environ[\"CUDA_DEVICE_ORDER\"]=\"PCI_BUS_ID\"
os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"1\" #model will be trained on GPU 1

Charger les données

Commencez par importer les modules requis comme cv2, numpy, matplotlib et surtout keras, puisque c’est le framework utilisé dans ce tutoriel.

import cv2
import matplotlib.pyplot as plt
%matplotlib inline
from skimage.filters import threshold_otsu
import numpy as np
from glob import glob
from scipy import misc
from matplotlib.patches import Circle,Ellipse
from matplotlib.patches import Rectangle
import os
from PIL import Image
import keras
from matplotlib import pyplot as plt
import numpy as np
import gzip
%matplotlib inline
from keras.layers import Input,Conv2D,MaxPooling2D,UpSampling2D
from keras.models import Model
from keras.optimizers import RMSprop
from keras.layers.normalization import BatchNormalization
Using TensorFlow backend.
data = glob('FVC2002/Db*/*')
len(data)
3200
images = []
def read_images(data):
    for i in range(len(data)):
        img = misc.imread(data[i])
        img = misc.imresize(img,(224,224))
        images.append(img)
    return images
images = read_images(data)
images_arr = np.asarray(images)
images_arr = images_arr.astype('float32')
images_arr.shape
(3200, 224, 224)

Une fois les données chargées, vous pouvez analyser le jeu pour vous familiariser avec la matière que vous allez traiter dans ce tutoriel.

Exploration des données

Analysons l’aspect des images et leurs dimensions à l’aide de l’attribut .shape des tableaux NumPy :

# Shapes of training set
print(\"Dataset (images) shape: {shape}\".format(shape=images_arr.shape))
Dataset (images) shape: (3200, 224, 224)

Comme indiqué ci-dessus, les données ont une forme 3200 x 224 x 224 : 3200 échantillons, chacun étant une matrice 224 x 224.

Regardons maintenant quelques images du jeu de données :

#plt.figure(figsize=[5,5])

# Display the first image in training data
for i in range(2):
    plt.figure(figsize=[5, 5])
    curr_img = np.reshape(images_arr[i], (224,224))
    plt.imshow(curr_img, cmap='gray')
    plt.show()
\"fingerprint\"
\"fingerprint\"

Les deux sorties ci-dessus proviennent du jeu de données. Les empreintes ne sont pas très nettes. Il sera intéressant de voir si l’autoencodeur convolutionnel parvient à apprendre les caractéristiques et à reconstruire correctement ces images.

Prétraitement des données

Les images sont en niveaux de gris, avec des valeurs de pixels entre 0 et 255 et une dimension de 224 x 224. Avant de les fournir au modèle, un prétraitement s’impose. Vous allez d’abord convertir chaque image 224 x 224 en une matrice 224 x 224 x 1, exploitable par le réseau :

images_arr = images_arr.reshape(-1, 224,224, 1)
images_arr.shape
(3200, 224, 224, 1)

Vérifiez ensuite le type de données du tableau NumPy : il doit être en float32. Si ce n’est pas le cas, convertissez-le. Puis, remettez à l’échelle les pixels sur l’intervalle 0 à 1 inclus. Allons-y.

D’abord, vérifions le type :

images_arr.dtype
dtype('float32')

Puis, normalisez à l’aide de la valeur maximale des pixels :

np.max(images_arr)
255.0
images_arr = images_arr / np.max(images_arr)

Vérifions que les bornes min et max valent bien 0,0 et 1,0 après la mise à l’échelle.

np.max(images_arr), np.min(images_arr)
(1.0, 0.0)

Ensuite, partitionnez les données. Pour favoriser la généralisation, scindez en deux ensembles : entraînement et validation. Vous entraînerez le modèle sur 80 % des données et le validerez sur 20 %.

Cela réduit aussi le risque de surapprentissage, car la validation s’effectue sur des exemples non vus pendant l’entraînement.

Utilisez train_test_split de scikit-learn pour faire une séparation propre :

from sklearn.model_selection import train_test_split
train_X,valid_X,train_ground,valid_ground = train_test_split(images_arr,
                                                             images_arr,
                                                             test_size=0.2,
                                                             random_state=13)

Remarque : pour cette tâche, vous n’avez pas besoin d’étiquettes d’entraînement ni de test. C’est pourquoi vous passez deux fois les images d’entraînement. Elles servent à la fois d’entrées et de vérité terrain, comme les étiquettes dans une tâche de classification.

Vous pouvez maintenant définir le réseau et y injecter les données. Passons à l’étape suivante.

Réseau : l’autoencodeur convolutionnel

Les images sont de taille 224 x 224 x 1, soit un vecteur de 50 176 dimensions. Vous convertissez la matrice en tableau, la normalisez entre 0 et 1, la redimensionnez en 224 x 224 x 1, puis l’utilisez comme entrée du réseau.

Vous utiliserez une taille de lot (batch size) de 128. Des tailles plus élevées (256 ou 512) peuvent aussi convenir, selon votre machine. Ce choix influence fortement l’apprentissage et la précision. L’entraînement se fera sur 50 époques.

batch_size = 128
epochs = 200
inChannel = 1
x, y = 224, 224
input_img = Input(shape = (x, y, inChannel))

Comme vous le savez, l’autoencodeur se compose de deux parties : un encodeur et un décodeur.

Encodeur

  • Première couche : 32 filtres 3 x 3, suivie d’un downsampling (max-pooling),
  • Deuxième couche : 64 filtres 3 x 3, suivie d’un autre downsampling,
  • Dernière couche de l’encodeur : 128 filtres 3 x 3.

Décodeur

  • Première couche : 128 filtres 3 x 3, suivie d’un upsampling,
  • Deuxième couche : 64 filtres 3 x 3, suivie d’un autre upsampling,
  • Dernière couche : un filtre 3 x 3.

Chaque couche de max-pooling réduit les dimensions par un facteur 2, tandis que chaque couche d’upsampling les augmente d’un facteur 2.

Remarque : le nombre de filtres, la taille des filtres, le nombre de couches, le nombre d’époques, etc., sont des hyperparamètres. Ajustez-les selon votre intuition, testez des variantes et mesurez les performances. C’est ainsi que vous affinerez votre pratique du deep learning.

def autoencoder(input_img):
    #encoder
    #input = 28 x 28 x 1 (wide and thin)
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img) #28 x 28 x 32
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1) #14 x 14 x 32
    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2) #7 x 7 x 64
    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2) #7 x 7 x 128 (small and thick)

    #decoder
    conv4 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3) #7 x 7 x 128
    up1 = UpSampling2D((2,2))(conv4) # 14 x 14 x 128
    conv5 = Conv2D(64, (3, 3), activation='relu', padding='same')(up1) # 14 x 14 x 64
    up2 = UpSampling2D((2,2))(conv5) # 28 x 28 x 64
    decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2) # 28 x 28 x 1
    return decoded

Après avoir créé le modèle, compilez-le avec l’optimiseur RMSProp.

Spécifiez aussi la fonction de perte via l’argument loss. Ici, il s’agit de l’erreur quadratique moyenne (MSE), calculée pixel par pixel entre la sortie prédite du lot et la vérité terrain :

autoencoder = Model(input_img, autoencoder(input_img))
autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())

Visualisons l’architecture avec summary : vous obtiendrez le nombre de paramètres (poids et biais) par couche et le total du modèle.

autoencoder.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 224, 224, 1)       0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 224, 224, 32)      320       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 112, 112, 32)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 112, 112, 64)      18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 56, 56, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 56, 56, 128)       73856     
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 56, 56, 128)       147584    
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 112, 112, 128)     0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 112, 112, 64)      73792     
_________________________________________________________________
up_sampling2d_2 (UpSampling2 (None, 224, 224, 64)      0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 224, 224, 1)       577       
=================================================================
Total params: 314,625
Trainable params: 314,625
Non-trainable params: 0
_________________________________________________________________

Il est temps d’entraîner le modèle avec la fonction fit() de Keras. L’entraînement dure 200 époques. La fonction fit() renvoie un objet history. En le stockant dans autoencoder_train, vous pourrez tracer ensuite les pertes d’entraînement et de validation pour analyser visuellement les performances.

Entraîner le modèle

autoencoder_train = autoencoder.fit(train_X, train_ground, batch_size=batch_size,epochs=epochs,verbose=1,validation_data=(valid_X, valid_ground))
Train on 2560 samples, validate on 640 samples
Epoch 1/200
2560/2560 [==============================] - 17s - loss: 0.0677 - val_loss: 0.0498
Epoch 2/200
2560/2560 [==============================] - 11s - loss: 0.0369 - val_loss: 0.0287
...
2560/2560 [==============================] - 11s - loss: 0.0029 - val_loss: 0.0024
Epoch 200/200
2560/2560 [==============================] - 11s - loss: 0.0027 - val_loss: 0.0028

Parfait ! Vous avez entraîné le modèle sur le jeu d’empreintes pendant 200 époques. Traçons maintenant les courbes de perte entraînement/validation pour visualiser la performance.

loss = autoencoder_train.history['loss']
val_loss = autoencoder_train.history['val_loss']
epochs = range(200)
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
\"training

On observe que les pertes d’entraînement et de validation évoluent de concert. Le modèle ne surapprend pas : la perte de validation diminue et ne s’accroît pas, et l’écart entre les deux reste faible, notamment après la 40e époque.

Vous pouvez donc conclure que la capacité de généralisation du modèle est satisfaisante.

Il est temps de reconstruire les images de test avec la fonction predict() de Keras et d’évaluer la qualité de reconstruction.

Enregistrer le modèle

Enregistrons le modèle entraîné. C’est une étape clé en deep learning, car les poids constituent le cœur de votre solution.

Vous pouvez recharger ces poids à tout moment dans le même modèle et reprendre l’entraînement là où il s’est arrêté. Par exemple, en réentraînant le modèle ci-dessus, les paramètres (poids, biais, fonction de perte, etc.) ne repartiront pas de zéro : ce ne sera plus un apprentissage « from scratch ».

En une seule ligne de code, vous pouvez sauvegarder puis recharger les poids.

autoencoder = autoencoder.save_weights('autoencoder.h5')
autoencoder = Model(input_img, autoencoder(input_img))
autoencoder.load_weights('autoencoder.h5')
autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())

Prédire sur les données de validation

Comme vous n’avez pas d’ensemble de test distinct ici, utilisons les données de validation pour évaluer le modèle que vous venez d’entraîner.

Vous effectuerez des prédictions sur les 640 images de validation et afficherez quelques reconstructions pour juger visuellement la qualité.

pred = autoencoder.predict(valid_X)
plt.figure(figsize=(20, 4))
print(\"Test Images\")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(valid_ground[i, ..., 0], cmap='gray')
plt.show()    
plt.figure(figsize=(20, 4))
print(\"Reconstruction of Test Images\")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(pred[i, ..., 0], cmap='gray')  
plt.show()
Test Images
\"Test
Reconstruction of Test Images
\"Reconstruction

À la vue des figures ci-dessus, votre modèle reconstruit très bien les images de test. Visuellement, les originaux et leurs reconstructions sont presque identiques.

Prédire sur deux types de capteurs avec le modèle entraîné

Le graphique entraînement vs validation montre une bonne généralisation sur des données non vues. Testons maintenant la robustesse du modèle sur des données issues d’autres capteurs.

Vous allez l’évaluer sur deux types de capteurs :

  • Secugen
  • Lumidigm

Commençons par un capteur d’empreintes de qualité inférieure, Secugen, et voyons comment le modèle s’en sort.

sec = glob('Secugen/*')
images = []
def read_images(data):
    for i in range(len(data)):
        img = misc.imread(data[i])
        img = misc.imresize(img,(224,224))
        images.append(img)
    return images
images = read_images(sec)
secugen = np.asarray(images)
secugen = secugen.astype('float32')
images_arr.shape
(48, 224, 224, 1)
secugen = secugen / np.max(secugen)
secugen = secugen.reshape(-1, 224,224, 1)
pred = autoencoder.predict(secugen)
plt.figure(figsize=(20, 4))
print(\"Test Secugen Images\")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(secugen[i, ..., 0], cmap='gray')
plt.show()    
plt.figure(figsize=(20, 4))
print(\"Reconstruction of Test Secugen Images\")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(pred[i, ..., 0], cmap='gray')  
plt.show()
Test Secugen Images
\"Test
Reconstruction of Test Secugen Images
\"Reconstruction

Comme on le voit, le modèle reconstruit très bien les images Secugen prédites. Plutôt bluffant, non ?

Passons maintenant à des images issues d’un capteur de meilleure qualité, Lumidigm.

lum = glob('Lumidigm/*')
images = []
def read_images(data):
    for i in range(len(data)):
        img = misc.imread(data[i])
        img = misc.imresize(img,(224,224))
        images.append(img)
    return images
images = read_images(lum)
lumidigm = np.asarray(images)
lumidigm = lumidigm.astype('float32')
lumidigm.shape
(48, 224, 224)
lumidigm = lumidigm / np.max(lumidigm)
lumidigm = lumidigm.reshape(-1, 224,224, 1)
pred = autoencoder.predict(lumidigm)
plt.figure(figsize=(20, 4))
print(\"Test Lumidigm Images\")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(lumidigm[i, ..., 0], cmap='gray')
plt.show()    
plt.figure(figsize=(20, 4))
print(\"Reconstruction of Test Lumidigm Images\")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(pred[i, ..., 0], cmap='gray')  
plt.show()
Test Lumidigm Images
\"Test
Reconstruction of Test Lumidigm Images
\"Reconstruction

Ici encore, le modèle reconstruit très correctement les images Lumidigm prédites.

Pour aller plus loin

Ce tutoriel vous a permis de lire des images depuis zéro, de les analyser, de les prétraiter et de les injecter dans un modèle sur un jeu d’empreintes. Vous avez vu une application concrète des autoencodeurs. Si vous avez pu suivre sans difficulté — ou même avec un peu d’efforts — bravo !

Dans le prochain tutoriel, vous apprendrez à lire des images médicales en modalité T-1 et à les reconstruire avec un autoencodeur.

Il reste encore beaucoup à explorer. Pourquoi ne pas suivre le cours Deep Learning in Python de DataCamp si ce n’est pas déjà fait ? Vous partirez des bases pour progresser vers la maîtrise du deep learning. Ce sera une ressource indispensable pour travailler avec des réseaux de neurones convolutionnels en Python, détecter des visages, des objets, etc.

Sujets
Python
Apprentissage profond

Cours Python

Cours

Introduction à Python

4 h
7M
Apprenez les bases de l’analyse de données avec Python en quatre heures et explorez ses principaux packages.
Afficher les détailsRight Arrow
Commencer Le Cours
Voir plusRight Arrow