[PYTHON] Segmentation d'image à l'aide de U-net

L'environnement utilise python3.7 et Tensorflow 2.1.1 avec le contenu suivant.

Qu'est-ce que la segmentation d'image?

VOC2012 Regardons un exemple de segmentation d'image dans un ensemble de données (voir ci-dessous). Screen Shot 2020-10-29 at 22.17.07.png Dans cet exemple, les pixels de l'image sont classés comme vélo, conducteur ou arrière-plan. Cette façon de classer les images ** en unités de pixels ** est appelée segmentation d'image (ou segmentation sémantique).

Lorsque vous commencez à étudier les réseaux de neurones, vous implémentez souvent la reconnaissance de caractères manuscrits MNIST dans les didacticiels. Dans ce cas, l'image est entrée et la probabilité que l'image soit un nombre de 0 à 9 est sortie, de sorte que la couche de sortie finale est de 10 nœuds unidimensionnels.

Par contre, dans le cas de la segmentation d'image, nous voulons classer chaque pixel de l'image, donc le calque de sortie final a la dimension (taille verticale de l'image, taille horizontale de l'image, nombre de classes). U-net, que je présenterai cette fois, peut fournir une excellente précision pour de tels problèmes de segmentation d'image.

U-net U-net est un modèle utilisant CNN et skip connection publié dans l'article "U-Net: Convolutional Networks for Biomedical Image Segmentation". (Figure ci-dessous).

Screen Shot 2020-10-29 at 22.44.45.png

L'idée de base d'U-net est ・ Extraction de caractéristiques tout en rendant l'image rugueuse avec la couche de pliage et la couche de mise en commun -Restaurer les informations en unités de pixels avec saut de connexion (la partie connectée horizontalement dans la figure ci-dessus) est. En particulier, la connexion de saut est au cœur de ce U-net, et sans cette connexion de saut, il semble que les informations en unités de pixels ne puissent pas être beaucoup retenues en raison de l'effet de grossissement et la précision de la segmentation diminue. U-net est maintenant utilisé dans le cadre de réseaux de neurones plus complexes comme base de la segmentation d'image.

Implémentation U-net

Commençons par écrire la première partie de U-net (les trois couches supérieures gauche dans la figure ci-dessus). Dans l'implémentation suivante, la taille de l'image d'entrée est définie sur (256, 256).

from tensorflow import keras
from tensorflow.keras import layers

inputs = keras.Input(shape=(256, 256, 1), name="img")
x = layers.Conv2D(64, 3, activation="relu", padding="same", kernel_initializer='he_normal')(inputs)
block_1_output = layers.Conv2D(64, 3, activation="relu", padding="same")(x)

Une image de taille (256, 256, 1) est contournée avec un réseau neuronal convolutif (CNN) avec 64 canaux et une taille de filtre de 3. La fonction d'activation étant un modèle profond, nous utilisons ReLU. Conservez la sortie nommée block_1_output pour une connexion ultérieure. La couche suivante de U-net serait la suivante.

x = layers.MaxPooling2D(pool_size=(2, 2), padding="same")(block_1_output)
x = layers.Conv2D(128, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_2_output = layers.Conv2D(128, 3, activation="relu", padding="same")(x)

Il reçoit "block_1_output", le grossit avec Max Pooling, augmente le nombre de canaux avec CNN et le convolve. Enregistrez "block_2_output" pour ignorer la connexion comme auparavant. Si vous écrivez U-net dans cette condition, l'image entière sera la suivante.

from tensorflow import keras
from tensorflow.keras import layers

inputs = keras.Input(shape=(256, 256, 1), name="img")
x = layers.Conv2D(64, 3, activation="relu", padding="same", kernel_initializer='he_normal')(inputs)
block_1_output = layers.Conv2D(64, 3, activation="relu", padding="same")(x)

x = layers.MaxPooling2D(pool_size=(2, 2), padding="same")(block_1_output)
x = layers.Conv2D(128, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_2_output = layers.Conv2D(128, 3, activation="relu", padding="same")(x)

x = layers.MaxPooling2D(pool_size=(2, 2), padding="same")(block_2_output)
x = layers.Conv2D(256, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_3_output = layers.Conv2D(256, 3, activation="relu", padding="same")(x)

x = layers.MaxPooling2D(pool_size=(2, 2), padding="same")(block_3_output)
x = layers.Conv2D(512, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_4_output = layers.Conv2D(512, 3, activation="relu", padding="same")(x)

x = layers.Dropout(0.5)(block_4_output)
x = layers.MaxPooling2D(pool_size=(2, 2), padding="same")(x)
x = layers.Conv2D(1024, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_5_output = layers.Conv2D(1024, 3, activation="relu", padding="same")(x)

x = layers.Dropout(0.5)(block_5_output)
x = layers.UpSampling2D(size=(2,2))(x)
x = layers.Conv2D(512, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
x = tf.concat([x, block_4_output], axis=3)
x = layers.Conv2D(512, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_6_output = layers.Conv2D(512, 3, activation="relu", padding="same")(x)

x = layers.UpSampling2D(size=(2,2))(block_6_output)
x = layers.Conv2D(256, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
x = tf.concat([x, block_3_output], axis=3)
x = layers.Conv2D(256, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_7_output = layers.Conv2D(256, 3, activation="relu", padding="same")(x)

x = layers.UpSampling2D(size=(2,2))(block_7_output)
x = layers.Conv2D(128, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
x = tf.concat([x, block_2_output], axis=3)
x = layers.Conv2D(128, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
block_8_output = layers.Conv2D(128, 3, activation="relu", padding="same")(x)

x = layers.UpSampling2D(size=(2,2))(block_8_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
x = tf.concat([x, block_1_output], axis=3)
x = layers.Conv2D(64, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
x = layers.Conv2D(64, 3, activation="relu", padding="same",kernel_initializer='he_normal')(x)
outputs = layers.Conv2D(1, 1, activation="sigmoid", padding="same")(x)

model = keras.Model(inputs, outputs, name="u-net")

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

Cela fait longtemps que U-net n'est pas compliqué, mais ce que je fais, c'est répéter des choses similaires. Où vous connecter avec sauter la connexion, utilisez tf.concat pour vous connecter avec la partie canal comme axe. L'image du réseau U-net n'inclut pas la couche Dropout, mais quand je lis le texte de l'article,

Drop-out layers at the end of the contracting path perform further >implicit data augmentation

Comme c'est le cas, la couche Dropout est également incluse dans le réseau ci-dessus. De plus, en supposant une segmentation en deux classes, binary_crossentropy est utilisé avec le nombre de canaux de sortie défini sur 1. Vous pouvez également en apprendre davantage sur la sortie en utilisant sparse_categorical_crossentropy avec 2 canaux.

Vérifions si le modèle est fait correctement.

tf.keras.utils.plot_model(model)

Si vous sortez le modèle avec, ce sera comme suit. Unknown-4.png C'est très long, mais vous pouvez voir que la première demi-couche et la deuxième demi-couche sont correctement connectées par saut de connexion. Lorsque vous sortez le modèle avec model.summary (), cela ressemble à ceci.

model.summary()

Model: "u-net"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
img (InputLayer)                [(None, 256, 256, 1) 0                                            
__________________________________________________________________________________________________
conv2d (Conv2D)                 (None, 256, 256, 64) 640         img[0][0]                        
__________________________________________________________________________________________________
conv2d_1 (Conv2D)               (None, 256, 256, 64) 36928       conv2d[0][0]                     
__________________________________________________________________________________________________
max_pooling2d (MaxPooling2D)    (None, 128, 128, 64) 0           conv2d_1[0][0]                   
__________________________________________________________________________________________________
conv2d_2 (Conv2D)               (None, 128, 128, 128 73856       max_pooling2d[0][0]              
__________________________________________________________________________________________________
conv2d_3 (Conv2D)               (None, 128, 128, 128 147584      conv2d_2[0][0]                   
__________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)  (None, 64, 64, 128)  0           conv2d_3[0][0]                   
__________________________________________________________________________________________________
conv2d_4 (Conv2D)               (None, 64, 64, 256)  295168      max_pooling2d_1[0][0]            
__________________________________________________________________________________________________
conv2d_5 (Conv2D)               (None, 64, 64, 256)  590080      conv2d_4[0][0]                   
__________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)  (None, 32, 32, 256)  0           conv2d_5[0][0]                   
__________________________________________________________________________________________________
conv2d_6 (Conv2D)               (None, 32, 32, 512)  1180160     max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_7 (Conv2D)               (None, 32, 32, 512)  2359808     conv2d_6[0][0]                   
__________________________________________________________________________________________________
dropout (Dropout)               (None, 32, 32, 512)  0           conv2d_7[0][0]                   
__________________________________________________________________________________________________
max_pooling2d_3 (MaxPooling2D)  (None, 16, 16, 512)  0           dropout[0][0]                    
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 16, 16, 1024) 4719616     max_pooling2d_3[0][0]            
__________________________________________________________________________________________________
conv2d_9 (Conv2D)               (None, 16, 16, 1024) 9438208     conv2d_8[0][0]                   
__________________________________________________________________________________________________
dropout_1 (Dropout)             (None, 16, 16, 1024) 0           conv2d_9[0][0]                   
__________________________________________________________________________________________________
up_sampling2d (UpSampling2D)    (None, 32, 32, 1024) 0           dropout_1[0][0]                  
__________________________________________________________________________________________________
conv2d_10 (Conv2D)              (None, 32, 32, 512)  4719104     up_sampling2d[0][0]              
__________________________________________________________________________________________________
tf_op_layer_concat (TensorFlowO [(None, 32, 32, 1024 0           conv2d_10[0][0]                  
                                                                 conv2d_7[0][0]                   
__________________________________________________________________________________________________
conv2d_11 (Conv2D)              (None, 32, 32, 512)  4719104     tf_op_layer_concat[0][0]         
__________________________________________________________________________________________________
conv2d_12 (Conv2D)              (None, 32, 32, 512)  2359808     conv2d_11[0][0]                  
__________________________________________________________________________________________________
up_sampling2d_1 (UpSampling2D)  (None, 64, 64, 512)  0           conv2d_12[0][0]                  
__________________________________________________________________________________________________
conv2d_13 (Conv2D)              (None, 64, 64, 256)  1179904     up_sampling2d_1[0][0]            
__________________________________________________________________________________________________
tf_op_layer_concat_1 (TensorFlo [(None, 64, 64, 512) 0           conv2d_13[0][0]                  
                                                                 conv2d_5[0][0]                   
__________________________________________________________________________________________________
conv2d_14 (Conv2D)              (None, 64, 64, 256)  1179904     tf_op_layer_concat_1[0][0]       
__________________________________________________________________________________________________
conv2d_15 (Conv2D)              (None, 64, 64, 256)  590080      conv2d_14[0][0]                  
__________________________________________________________________________________________________
up_sampling2d_2 (UpSampling2D)  (None, 128, 128, 256 0           conv2d_15[0][0]                  
__________________________________________________________________________________________________
conv2d_16 (Conv2D)              (None, 128, 128, 128 295040      up_sampling2d_2[0][0]            
__________________________________________________________________________________________________
tf_op_layer_concat_2 (TensorFlo [(None, 128, 128, 25 0           conv2d_16[0][0]                  
                                                                 conv2d_3[0][0]                   
__________________________________________________________________________________________________
conv2d_17 (Conv2D)              (None, 128, 128, 128 295040      tf_op_layer_concat_2[0][0]       
__________________________________________________________________________________________________
conv2d_18 (Conv2D)              (None, 128, 128, 128 147584      conv2d_17[0][0]                  
__________________________________________________________________________________________________
up_sampling2d_3 (UpSampling2D)  (None, 256, 256, 128 0           conv2d_18[0][0]                  
__________________________________________________________________________________________________
conv2d_19 (Conv2D)              (None, 256, 256, 64) 73792       up_sampling2d_3[0][0]            
__________________________________________________________________________________________________
tf_op_layer_concat_3 (TensorFlo [(None, 256, 256, 12 0           conv2d_19[0][0]                  
                                                                 conv2d_1[0][0]                   
__________________________________________________________________________________________________
conv2d_20 (Conv2D)              (None, 256, 256, 64) 73792       tf_op_layer_concat_3[0][0]       
__________________________________________________________________________________________________
conv2d_21 (Conv2D)              (None, 256, 256, 64) 36928       conv2d_20[0][0]                  
__________________________________________________________________________________________________
conv2d_22 (Conv2D)              (None, 256, 256, 1)  65          conv2d_21[0][0]                  
==================================================================================================
Total params: 34,512,193
Trainable params: 34,512,193
Non-trainable params: 0
__________________________________________________________________________________________________

Le nombre total de paramètres est de 34 512 193!, Ce qui est un nombre considérable.

Apprentissage

Veuillez vous référer à Article précédent pour la préparation des données d'entrée telles que le remplissage de données. Découpez les données d'image de ISBI challenge 2012 (Segmentation of neuronal structures in EM stacks) en patchs de (256, 256) et effectuez une augmentation des données avec ImageDataGenerator. Il y a.

Maintenant, apprenons comme suit. my_generator est un générateur qui transmet les données d'image d'entraînement et my_val_gen est un générateur qui transmet les données d'image de vérification.

EPOCHS = 200
STEPS_PER_EPOCH = 300
VALIDATION_STEPS = STEPS_PER_EPOCH//10
model_history = model.fit(my_generator, epochs=EPOCHS,
                          steps_per_epoch=STEPS_PER_EPOCH,
                          validation_steps=VALIDATION_STEPS,
                          validation_data=my_val_gen)

L'état d'apprentissage est le suivant. L'axe horizontal est le nombre d'EPOCHS et l'axe vertical est la précision ou la perte. La perte d'entraînement et la précision de l'entraînement diminuent de manière monotone, ce qui indique que l'apprentissage progresse. La précision de la validation est également supérieure à 90% et il semble y avoir des performances prédictives. Si vous regardez de près, la précision / perte de validation est le maximum / minimum par 20 à 30 étapes. Cela suggère qu'il est surajusté à mesure que le nombre d'EPOCHS augmente encore. history.png

Regardons la segmentation des données de test à l'aide d'un modèle par numéro EPOCHS 30.

def create_mask(pred_img):
  pred_img = tf.math.greater(pred_img, 0.5)
  return pred_img[0]

plt.figure(dpi=150)
plt.subplot(1, 2, 1)
plt.imshow(test_image[:256, :256], cmap="gray")

plt.subplot(1, 2, 2)
test_image = test_image[:256,:256,tf.newaxis]
test_image = np.expand_dims(test_image, axis=0)
pred_img = model.predict(test_image)
masked_img = create_mask(pred_img)
plt.imshow(masked_img[:,:,0])

1.jpg En revanche, la segmentation des données de formation était la suivante. 2.jpg Vous pouvez voir que la segmentation fonctionne bien pour les données d'entraînement et les données de test. Cependant, par rapport aux données d'apprentissage, y a-t-il du bruit dans les résultats de segmentation des données de test? Je pense. À cet égard, il peut être nécessaire de concevoir un peu plus sur le modèle et la méthode d'augmentation des données.

À la fin

Nous avons introduit l'implémentation en utilisant tensorflow pour la segmentation d'image à l'aide de U-net. J'ai implémenté le modèle U-net du papier original tel quel, mais il existe différentes techniques qui peuvent être utilisées telles que la normalisation par lots, donc j'écrirai à nouveau une introduction si j'ai une chance.

Recommended Posts

Segmentation d'image à l'aide de U-net
Essayez d'utiliser l'image Docker de Jupyter
Prédiction d'images dans le cloud à l'aide de convLSTM
Générer une image Docker à l'aide de Fabric
Segmentation SLIC Superpixel dans une image scikit
Implémentation de la segmentation d'image en python (Union-Find)
Jugement de l'image rétroéclairée avec OpenCV
[Python] Utilisation d'OpenCV avec Python (transformation d'image)
Segmentation d'image avec scikit-image et scikit-learn
Grattage écologique grâce au traitement d'image
[FSL] Mesure d'image à l'aide du ROI (VOI)
Binarisation d'images par analyse discriminante linéaire
Reconnaissance d'image des fruits avec VGG16
Python: principes de base de la reconnaissance d'image à l'aide de CNN
Discrimination d'image d'icône Pokemon à l'aide des fonctionnalités HOG
Estimation de catégorie à l'aide de l'API de reconnaissance d'image de docomo
Python: Application de la reconnaissance d'image à l'aide de CNN
Inférence d'image de visage à l'aide de Flask et TensorFlow
Reconnaissance d'image à l'aide de chevaux et de cerfs CNN
Segment d'image utilisant Oxford_iiit_pet dans Google Colab
Collection d'images à l'aide de l'API Google Custom Search
Segmentation d'image avec CaDIS: un ensemble de données sur la cataracte
(Lecture du papier) Colorisation d'image prenant en charge les instances (Division de la région: imagerie couleur utilisant la segmentation d'instance)