L'architecture U-Net est une architecture de réseau de neurones convolutifs (CNN) spécifiquement conçue pour la segmentation d'images, c'est-à-dire la tâche de prédire pour chaque pixel d'une image à quelle classe il appartient. Développée initialement pour la segmentation d'images biomédicales, U-Net a rapidement gagné en popularité dans divers domaines en raison de sa performance robuste, surtout dans les cas où les données d'entraînement sont limitées.

Structure de l'U-Net

Le réseau U-Net tire son nom de sa forme distinctive en "U". Cette architecture comporte deux chemins : un chemin contractant  (ou de descente) qui capture le contexte et un chemin symétrique d'expansion (ou de montée) qui permet une localisation précise. La dénomination "U-Net" est donc directement inspirée par cette conception architecturale, où la forme en "U" est centrale pour la fonctionnalité du réseau.

 

Figure 1

Description détaillée de l'architecture U-Net :

  1. Chemin de contraction (encodeur) :

    • L'entrée de l'U-Net est une image avec une résolution spatiale donnée et un certain nombre de canaux (par exemple, une image en niveaux de gris aurait un seul canal, tandis qu'une image en couleur aurait trois canaux pour le rouge, le vert et le bleu).
    • Le chemin de contraction se compose de blocs successifs, chacun contenant deux couches de convolution suivies d'une couche d'activation ReLU et d'une opération de max pooling. Chaque couche convolutive utilise des filtres de petite taille (typiquement 3x3) pour capturer les motifs locaux dans l'image.
    • À chaque étape de contraction, la résolution spatiale de l'image est réduite (à cause du max pooling), mais la profondeur des cartes de caractéristiques augmente (à cause de l'ajout de filtres). Cela permet de capturer des informations de plus en plus abstraites et globales de l'image.
  2. Partie la plus basse :

    • Au fond de l'U, il y a un bloc de convolution supplémentaire sans max pooling suivant. Cela représente le point de transition entre la contraction et l'expansion.
  3. Chemin d'expansion (décodeur) :

    • Le chemin d'expansion inverse le processus de contraction. Il commence par des opérations d'up-convolution (ou deconvolution), qui augmentent la résolution spatiale des caractéristiques.
    • Après chaque up-convolution, une opération de "copie et rognage" est effectuée, où les cartes de caractéristiques correspondantes du chemin de contraction sont copiées et fusionnées avec la sortie actuelle. Cela permet de restaurer les informations spatiales perdues lors de la contraction.
    • Le chemin d'expansion se poursuit avec des couches de convolution pour raffiner les caractéristiques et reconstruire l'image à sa résolution originale.
  4. Sortie :

    • La dernière couche du réseau est une couche convolutive avec un nombre de filtres égal au nombre de classes de segmentation désirées. Elle produit une carte de segmentation où chaque pixel est classifié dans une catégorie spécifique.

Ainsi, l'encodeur (chemin de contraction) réduit les dimensions spatiales (hauteur et largeur) et extrait des informations de haut niveau (profondeur en termes de nombre de canaux), tandis que le décodeur (chemin expansif) utilise ces informations pour reconstruire une carte de segmentation détaillée de l'image.

La structure en "U" est non seulement symétrique mais aussi fonctionnelle, car elle permet la fusion des caractéristiques de contexte à partir du chemin contractant avec des informations de localisation plus détaillées à partir du chemin d'expansion. Cela se fait par des connexions de saut (skip connections) qui copient des caractéristiques de la partie contractante et les combinent avec les caractéristiques dans la partie d'expansion correspondante. Cette combinaison de contexte et de localisation est cruciale pour effectuer une segmentation précise à l'échelle des pixels.

Le schéma (figure1) précédent montre comment le réseau procède à une analyse détaillée de l'image en réduisant progressivement sa dimension pour extraire des caractéristiques, puis reconstruit une sortie de haute résolution en réintégrant les informations de localisation. Voici une explication détaillée de chaque partie de cet exemple.

Partie gauche (contraction / encodeur) :

  • Entrée : L'image commence par être introduite dans le réseau avec sa dimension initiale (par exemple, 572x572 pixels).
  • Couches convolutives : Des couches de convolution (Conv 3x3, ReLU) sont appliquées, où "3x3" indique la taille du filtre convolutif et "ReLU" est la fonction d'activation utilisée. Ces couches extraient des caractéristiques de l'image à différents niveaux.
  • Pooling : Après chaque série de couches convolutives, une opération de max pooling (max pool 2x2) est appliquée, réduisant les dimensions spatiales par un facteur de 2 (d'où le "2x2") et augmentant la profondeur des cartes de caractéristiques. Ce processus est répété, créant une structure pyramidale qui réduit progressivement la dimension spatiale des données tout en augmentant la profondeur, c'est-à-dire le nombre de caractéristiques extraites.

Partie centrale (le fond du "U") :

  • La partie la plus basse de l'U correspond au niveau de contraction le plus profond, avec le plus grand nombre de caractéristiques (par exemple, 1024 caractéristiques) et la résolution spatiale la plus faible.

Partie droite (expansion / décodeur) :

  • Up-convolution : L'opération d'up-convolution (up-conv 2x2) augmente les dimensions spatiales des cartes de caractéristiques. C'est souvent réalisé par une convolution transposée.
  • Copie et rognage (Copy and crop) : Les caractéristiques de la partie de contraction correspondante sont copiées et fusionnées avec celles de la partie d'expansion. Ce processus permet de conserver l'information de localisation qui est cruciale pour la segmentation précise.
  • Couches convolutives : Après chaque up-convolution et fusion de caractéristiques, des couches convolutives sont à nouveau appliquées.
  • Sortie : Le réseau produit une carte de segmentation de l'image d'entrée, indiquant la classe de chaque pixel.

Flèches et symboles :

  • Flèches bleues : Indiquent le flux des données à travers les couches convolutives.
  • Flèches rouges : Indiquent les opérations de max pooling.
  • Flèches vertes : Indiquent les opérations d'up-convolution.
  • Flèches grises : Indiquent la copie et le rognage des caractéristiques pour la fusion avec les couches de l'expansion.

 

 Exemple d'implémentation de  l'architecture U-Net 

La mise en œuvre peut être divisée en trois parties. Tout d’abord, nous définirons le bloc codeur utilisé dans le chemin de contraction. Ce bloc se compose de deux couches de convolution 3 × 3 suivies d'une couche d'activation ReLU et d'une couche de pooling max 2 × 2. La deuxième partie est le bloc décodeur, qui prend la carte des caractéristiques de la couche inférieure, la convertit, la recadre et la concatène avec les données de l'encodeur du même niveau, puis exécute deux couches de convolution 3 × 3 suivies de l'activation de ReLU. La troisième partie consiste à définir le modèle U-Net à l'aide de ces blocs.

Code du bloc encodeur 

def encoder_block(inputs, num_filters): 

	# une Convolution avec un filter de 3x3 filter suivit d'une ReLU 
	x = tf.keras.layers.Conv2D(num_filters, 3, padding = 'valid')(inputs) 
	x = tf.keras.layers.Activation('relu')(x) 
	
	# Cune Convolution avec un filter de 3x3 filter suivit d'une ReLU  
	x = tf.keras.layers.Conv2D(num_filters,3, padding = 'valid')(x) 
	x = tf.keras.layers.Activation('relu')(x) 

	# un Max Pooling avec un filter de 2x2 
	x = tf.keras.layers.MaxPool2D(pool_size = (2, 2), strides = 2)(x) 
	
	return x

Code du bloc décodeur 

def decoder_block(inputs, skip_features, num_filters): 

	# Up-convolution c'est un Upsampling avec2x2 filter 
	x = tf.keras.layers.Conv2DTranspose(num_filters,(2, 2),strides = 2,padding = 'valid')(inputs) 
	
	# Copier et recadrer la connexion de saut, puis la concaténer 
	skip_features = tf.image.resize(skip_features,size = (x.shape[1],
											x.shape[2])) 
	x = tf.keras.layers.Concatenate()([x, skip_features]) 
	
	# Convolution avec un filter de 3x3 filter suivit d'une ReLU 
	x = tf.keras.layers.Conv2D(num_filters, 3,padding = 'valid')(x) 
	x = tf.keras.layers.Activation('relu')(x) 

	# Convolution avec un filter de 3x3 filter suivit d'une ReLU  
	x = tf.keras.layers.Conv2D(num_filters, 3, padding = 'valid')(x) 
	x = tf.keras.layers.Activation('relu')(x) 
	
	return x

 

# le code du modél Unet
import tensorflow as tf 

def unet_model(input_shape = (256, 256, 3), num_classes = 1): 
	inputs = tf.keras.layers.Input(input_shape) 
	
	# Cchemin contractuel  
	s1 = encoder_block(inputs, 64) 
	s2 = encoder_block(s1, 128) 
	s3 = encoder_block(s2, 256) 
	s4 = encoder_block(s3, 512) 
	
	# Bottleneck la partie la plus basse de l'architecture en U
	b1 = tf.keras.layers.Conv2D(1024, 3, padding = 'valid')(s4) 
	b1 = tf.keras.layers.Activation('relu')(b1) 
	b1 = tf.keras.layers.Conv2D(1024, 3, padding = 'valid')(b1) 
	b1 = tf.keras.layers.Activation('relu')(b1) 
	
	# chemin expansif 
	s5 = decoder_block(b1, s4, 512) 
	s6 = decoder_block(s5, s3, 256) 
	s7 = decoder_block(s6, s2, 128) 
	s8 = decoder_block(s7, s1, 64) 
	
	# Output 
	outputs = tf.keras.layers.Conv2D(num_classes,1,padding = 'valid',activation = 'sigmoid')(s8) 
	
	model = tf.keras.models.Model(inputs = inputs,outputs = outputs,name = 'U-Net') 
	return model 

 

model = unet_model(input_shape=(572, 572, 3), num_classes=2) 

model.summary()
 
 
 Model: "U-Net"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 input_1 (InputLayer)           [(None, 572, 572, 3  0           []                               
                                )]                                                                
                                                                                                  
 conv2d (Conv2D)                (None, 570, 570, 64  1792        ['input_1[0][0]']                
                                )                                                                 
                                                                                                  
 activation (Activation)        (None, 570, 570, 64  0           ['conv2d[0][0]']                 
                                )                                                                 
                                                                                                  
 conv2d_1 (Conv2D)              (None, 568, 568, 64  36928       ['activation[0][0]']             
                                )                                                                 
                                                                                                  
 activation_1 (Activation)      (None, 568, 568, 64  0           ['conv2d_1[0][0]']               
                                )                                                                 
                                                                                                  
 max_pooling2d (MaxPooling2D)   (None, 284, 284, 64  0           ['activation_1[0][0]']           
                                )                                                                 
                                                                                                  
 conv2d_2 (Conv2D)              (None, 282, 282, 12  73856       ['max_pooling2d[0][0]']          
                                8)                                                                
                                                                                                  
 activation_2 (Activation)      (None, 282, 282, 12  0           ['conv2d_2[0][0]']               
                                8)                                                                
                                                                                                  
 conv2d_3 (Conv2D)              (None, 280, 280, 12  147584      ['activation_2[0][0]']           
                                8)                                                                
                                                                                                  
 activation_3 (Activation)      (None, 280, 280, 12  0           ['conv2d_3[0][0]']               
                                8)                                                                
                                                                                                  
 max_pooling2d_1 (MaxPooling2D)  (None, 140, 140, 12  0          ['activation_3[0][0]']           
                                8)                                                                
                                                                                                  
 conv2d_4 (Conv2D)              (None, 138, 138, 25  295168      ['max_pooling2d_1[0][0]']        
                                6)                                                                
                                                                                                  
 activation_4 (Activation)      (None, 138, 138, 25  0           ['conv2d_4[0][0]']               
                                6)                                                                
                                                                                                  
 conv2d_5 (Conv2D)              (None, 136, 136, 25  590080      ['activation_4[0][0]']           
                                6)                                                                
                                                                                                  
 activation_5 (Activation)      (None, 136, 136, 25  0           ['conv2d_5[0][0]']               
                                6)                                                                
                                                                                                  
 max_pooling2d_2 (MaxPooling2D)  (None, 68, 68, 256)  0          ['activation_5[0][0]']           
                                                                                                  
 conv2d_6 (Conv2D)              (None, 66, 66, 512)  1180160     ['max_pooling2d_2[0][0]']        
                                                                                                  
 activation_6 (Activation)      (None, 66, 66, 512)  0           ['conv2d_6[0][0]']               
                                                                                                  
 conv2d_7 (Conv2D)              (None, 64, 64, 512)  2359808     ['activation_6[0][0]']           
                                                                                                  
 activation_7 (Activation)      (None, 64, 64, 512)  0           ['conv2d_7[0][0]']               
                                                                                                  
 max_pooling2d_3 (MaxPooling2D)  (None, 32, 32, 512)  0          ['activation_7[0][0]']           
                                                                                                  
 conv2d_8 (Conv2D)              (None, 30, 30, 1024  4719616     ['max_pooling2d_3[0][0]']        
                                )                                                                 
                                                                                                  
 activation_8 (Activation)      (None, 30, 30, 1024  0           ['conv2d_8[0][0]']               
                                )                                                                 
                                                                                                  
 conv2d_9 (Conv2D)              (None, 28, 28, 1024  9438208     ['activation_8[0][0]']           
                                )                                                                 
                                                                                                  
 activation_9 (Activation)      (None, 28, 28, 1024  0           ['conv2d_9[0][0]']               
                                )                                                                 
                                                                                                  
 conv2d_transpose (Conv2DTransp  (None, 56, 56, 512)  2097664    ['activation_9[0][0]']           
 ose)                                                                                             
                                                                                                  
 tf.image.resize (TFOpLambda)   (None, 56, 56, 512)  0           ['max_pooling2d_3[0][0]']        
                                                                                                  
 concatenate (Concatenate)      (None, 56, 56, 1024  0           ['conv2d_transpose[0][0]',       
                                )                                 'tf.image.resize[0][0]']        
                                                                                                  
 conv2d_10 (Conv2D)             (None, 54, 54, 512)  4719104     ['concatenate[0][0]']            
                                                                                                  
 activation_10 (Activation)     (None, 54, 54, 512)  0           ['conv2d_10[0][0]']              
 
 
                                                                                                  
 conv2d_11 (Conv2D)             (None, 52, 52, 512)  2359808     ['activation_10[0][0]']          
                                                                                                  
 activation_11 (Activation)     (None, 52, 52, 512)  0           ['conv2d_11[0][0]']              
                                                                                                  
 conv2d_transpose_1 (Conv2DTran  (None, 104, 104, 25  524544     ['activation_11[0][0]']          
 spose)                         6)                                                                
                                                                                                  
 tf.image.resize_1 (TFOpLambda)  (None, 104, 104, 25  0          ['max_pooling2d_2[0][0]']        
                                6)                                                                
                                                                                                  
 concatenate_1 (Concatenate)    (None, 104, 104, 51  0           ['conv2d_transpose_1[0][0]',     
                                2)                                'tf.image.resize_1[0][0]']      
                                                                                                  
 conv2d_12 (Conv2D)             (None, 102, 102, 25  1179904     ['concatenate_1[0][0]']          
                                6)                                                                
                                                                                                  
 activation_12 (Activation)     (None, 102, 102, 25  0           ['conv2d_12[0][0]']              
                                6)                                                                
                                                                                                  
 conv2d_13 (Conv2D)             (None, 100, 100, 25  590080      ['activation_12[0][0]']          
                                6)                                                                
                                                                                                  
 activation_13 (Activation)     (None, 100, 100, 25  0           ['conv2d_13[0][0]']              
                                6)                                                                
                                                                                                  
 conv2d_transpose_2 (Conv2DTran  (None, 200, 200, 12  131200     ['activation_13[0][0]']          
 spose)                         8)                                                                
                                                                                                  
 tf.image.resize_2 (TFOpLambda)  (None, 200, 200, 12  0          ['max_pooling2d_1[0][0]']        
                                8)                                                                
                                                                                                  
 concatenate_2 (Concatenate)    (None, 200, 200, 25  0           ['conv2d_transpose_2[0][0]',     
                                6)                                'tf.image.resize_2[0][0]']      
                                                                                                  
 conv2d_14 (Conv2D)             (None, 198, 198, 12  295040      ['concatenate_2[0][0]']          
                                8)                                                                
                                                                                                  
 activation_14 (Activation)     (None, 198, 198, 12  0           ['conv2d_14[0][0]']              
                                8)                                                                
                                                                                                  
 conv2d_15 (Conv2D)             (None, 196, 196, 12  147584      ['activation_14[0][0]']          
                                8)                                                                
                                                                                                  
 activation_15 (Activation)     (None, 196, 196, 12  0           ['conv2d_15[0][0]']              
                                8)                                                                
                                                                                                  
 conv2d_transpose_3 (Conv2DTran  (None, 392, 392, 64  32832      ['activation_15[0][0]']          
 spose)                         )                                                                 
                                                                                                  
 tf.image.resize_3 (TFOpLambda)  (None, 392, 392, 64  0          ['max_pooling2d[0][0]']          
                                )                                                                 
                                                                                                  
 concatenate_3 (Concatenate)    (None, 392, 392, 12  0           ['conv2d_transpose_3[0][0]',     
                                8)                                'tf.image.resize_3[0][0]']      
                                                                                                  
 conv2d_16 (Conv2D)             (None, 390, 390, 64  73792       ['concatenate_3[0][0]']          
                                )                                                                 
                                                                                                  
 activation_16 (Activation)     (None, 390, 390, 64  0           ['conv2d_16[0][0]']              
                                )                                                                 
                                                                                                  
 conv2d_17 (Conv2D)             (None, 388, 388, 64  36928       ['activation_16[0][0]']          
                                )                                                                 
                                                                                                  
 activation_17 (Activation)     (None, 388, 388, 64  0           ['conv2d_17[0][0]']              
                                )                                                                 
                                                                                                  
 conv2d_18 (Conv2D)             (None, 388, 388, 2)  130         ['activation_17[0][0]']          
                                                                                                  
==================================================================================================
Total params: 31,031,810
Trainable params: 31,031,810
Non-trainable params: 0
__________________________________________________________________________________________________


 
img = Image.open('palastine.png') 
img 
 
 
Out[11]:

 
import numpy as np 
from PIL import Image 
from tensorflow.keras.preprocessing import image 

 
img = Image.open('palastine.png') 
# preparation de l'image 
img = img.resize((572, 572)) 
img_array = image.img_to_array(img) 
img_array = np.expand_dims(img_array[:,:,:3], axis=0) 
img_array = img_array / 255.

# chargement du model 
model = unet_model(input_shape=(572, 572, 3), num_classes=2) 

#  predictions 
predictions = model.predict(img_array) 

# Convertir les predictions en un tableau numpy et modifier la taille en  taille originale de l'image  
predictions = np.squeeze(predictions, axis=0) 
predictions = np.argmax(predictions, axis=-1) 
predictions = Image.fromarray(np.uint8(predictions*255)) 
predictions = predictions.resize((img.width, img.height)) 

# sauvgarder l'image predit 
predictions.save('image_predite.jpg') 
predictions​
 
 
 
1/1 [==============================] - 3s 3s/step
 
Out[5]: