Pour les débutants, cet article vise à utiliser TensorFlow 2.0 pour la segmentation sémantique avec Deep Learning pour le moment. L'ensemble de données d'image utilise l'ensemble de données de segmentation de la chirurgie de la cataracte [^ 1] publié par Digital Surgery Ltd. De plus, le réseau sera SegNet [^ 2] avec le CNN à 10 couches utilisé précédent comme encodeur.
Les étiquettes de segmentation sont les suivantes. Dans le même temps, il montre également le pourcentage de chaque classe en pixels. Dans le tableau, vous pouvez voir que certaines classes n'existent pas dans chaque groupe. ~~ C'est ennuyeux! ~~ Le nombre d'images pour l'apprentissage, la vérification et le test est respectivement de 3584 (19 vidéos), 540 (3 vidéos) et 614 (3 vidéos).
Index | Class | Rapport de pixels(Apprentissage)[%] | Rapport de pixels(Vérification)[%] | Rapport de pixels(tester)[%] |
---|---|---|---|---|
0 | Pupil | 17.1 | 15.7 | 16.2 |
1 | Surgical Tape | 6.51 | 6.77 | 4.81 |
2 | Hand | 0.813 | 0.725 | 0.414 |
3 | Eye Retractors | 0.564 | 0.818 | 0.388 |
4 | Iris | 11.0 | 11.0 | 12.8 |
5 | Eyelid | 0 | 0 | 1.86 |
6 | Skin | 12.0 | 20.4 | 10.7 |
7 | Cornea | 49.6 | 42.2 | 50.6 |
8 | Hydro. Cannula | 0.138 | 0.0984 | 0.0852 |
9 | Visco. Cannula | 0.0942 | 0.0720 | 0.0917 |
10 | Cap. Cystotome | 0.0937 | 0.0821 | 0.0771 |
11 | Rycroft Cannula | 0.0618 | 0.0788 | 0.0585 |
12 | Bonn Forceps | 0.241 | 0.161 | 0.276 |
13 | Primary Knife | 0.123 | 0.258 | 0.249 |
14 | Phaco. Handpiece | 0.173 | 0.240 | 0.184 |
15 | Lens Injector | 0.343 | 0.546 | 0.280 |
16 | A/I Handpiece | 0.327 | 0.380 | 0.305 |
17 | Secondary Knife | 0.102 | 0.0933 | 0.148 |
18 | Micromanipulator | 0.188 | 0.229 | 0.215 |
19 | A/I Handpiece Handle | 0.0589 | 0.0271 | 0.0358 |
20 | Cap. Forceps | 0.0729 | 0.0144 | 0.0384 |
21 | Rycroft Cannula Handle | 0.0406 | 0.0361 | 0.0101 |
22 | Phaco. Handpiece Handle | 0.0566 | 0.00960 | 0.0202 |
23 | Cap. Cystotome Handle | 0.0170 | 0.0124 | 0.0287 |
24 | Secondary Knife Handle | 0.0609 | 0.0534 | 0.0124 |
25 | Lens Injector Handle | 0.0225 | 0.0599 | 0.0382 |
26 | Water Sprayer | 0.000448 | 0 | 0.00361 |
27 | Suture Needle | 0.000764 | 0 | 0 |
28 | Needle Holder | 0.0201 | 0 | 0 |
29 | Charleux Cannula | 0.00253 | 0 | 0.0164 |
30 | Vannas Scissors | 0.00107 | 0 | 0 |
31 | Primary Knife Handle | 0.000321 | 0 | 0.000385 |
32 | Viter. Handpiece | 0 | 0 | 0.0782 |
33 | Mendez Ring | 0.0960 | 0 | 0 |
34 | Biomarker | 0.00619 | 0 | 0 |
35 | Marker | 0.0661 | 0 | 0 |
De plus, un exemple d'image est présenté ci-dessous. L'image de segmentation brute est une image en échelle de gris avec l'index dans le tableau ci-dessus comme valeur de pixel.
<détails> Cet ensemble de données détermine les images (vidéos) à utiliser pour la formation, la validation et les tests. Les détails peuvent être trouvés dans un fichier appelé splits.txt dans l'ensemble de données. Par conséquent, le groupe divisé adopte le contenu de splits.txt et décrit le chemin du fichier de l'image chirurgicale et de l'image de segmentation de chaque groupe et leur correspondance dans le fichier csv avec le code suivant. Le fichier csv contenant les chemins d'accès aux fichiers pour les données d'entraînement, de vérification et de test est dans ce format. Tout d'abord, importez la bibliothèque que vous souhaitez utiliser. Ensuite, décrivez les paramètres et ainsi de suite. Ce qui suit est appliqué comme processus de rappel pendant l'apprentissage. Génère un générateur de données de formation et de validation. Utilisez ʻImageDataGenerator La fonction Le traitement de l'image de segmentation est effectué selon la procédure suivante. Dernière fois Construit SegNet avec la structure du CNN simple à 10 couches créé excluant toutes les connexions en tant qu'encodeur et la structure des encodeurs dans l'ordre inverse en tant que décodeur. Faire. Veuillez vous référer à ici pour l'explication de SegNet. Le reste est identique à Dernière fois. Entraînez-vous et enregistrez la courbe d'apprentissage. Les résultats d'apprentissage sont les suivants. L'évaluation est effectuée par IoU moyen pour chaque classe et IoU moyen qui est la moyenne d'entre eux. Le calcul a été fait avec le code suivant. Importation supplémentaire. L'inférence et l'évaluation sont effectuées selon la procédure suivante. Voici les résultats de l'évaluation. De plus, Io U moyen était de 15,0%. D'après l'article [^ 1], VGG est de 20,61%, donc je pense que c'est le cas. Dans cet article, nous avons effectué une segmentation sémantique de l'ensemble de données de segmentation de la chirurgie de la cataracte [^ 1] publié par Digital Surgery Ltd. en utilisant SegNet avec 8 couches chacune pour l'encodeur et le décodeur. Selon l'article [^ 1], il semble que 52,66% seront obtenus avec PSPNet, donc à l'avenir, sur la base de ce résultat, je viserai des performances identiques ou meilleures tout en incorporant les dernières méthodes telles que la structure du réseau et la méthode d'expansion des données.
Recommended Posts
4. Répartition des données
Code qui décrit le chemin du fichier image dans le fichier csv summary>
import os
from collections import defaultdict
import pandas as pd
#Créez un fichier csv qui décrit la correspondance entre les images et les étiquettes
def make_csv(fpath, dirlist):
#Examiner le chemin du fichier de l'image d'entraînement
dataset = defaultdict(list)
for dir in dirlist:
filelist = sorted(os.listdir(f'CaDIS/{dir}/Images'))
dataset['filename'] += list(map(lambda x: f'{dir}/Images/{x}', filelist))
filelist = sorted(os.listdir(f'CaDIS/{dir}/Labels'))
dataset['label'] += list(map(lambda x: f'{dir}/Labels/{x}', filelist))
#Enregistrer en tant que fichier csv
dataset = pd.DataFrame(dataset)
dataset.to_csv(fpath, index=False)
#Dossier vidéo des données d'entraînement
train_dir = ['Video01', 'Video03', 'Video04', 'Video06', 'Video08', 'Video09',
'Video10', 'Video11', 'Video13', 'Video14', 'Video15', 'Video17',
'Video18', 'Video20', 'Video21', 'Video22', 'Video23', 'Video24',
'Video25']
#Dossier vidéo des données de vérification
val_dir = ['Video05', 'Video07', 'Video16']
#Dossier vidéo des données de test
test_dir = ['Video02', 'Video12', 'Video19']
#Créez un fichier csv qui décrit la correspondance entre l'image des données d'entraînement et l'étiquette
make_csv('train.csv', train_dir)
#Créez un fichier csv qui décrit la correspondance entre l'image des données de vérification et l'étiquette
make_csv('val.csv', val_dir)
#Créez un fichier csv qui décrit la correspondance entre l'image des données d'entraînement et l'étiquette
make_csv('test.csv', test_dir)
filename
label
Video01/Images/Video1_frame000090.png
Video01/Labels/Video1_frame000090.png
Video01/Images/Video1_frame000100.png
Video01/Labels/Video1_frame000100.png
Video01/Images/Video1_frame000110.png
Video01/Labels/Video1_frame000110.png
5. Construction et apprentissage de modèles
import dataclasses
import math
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, MaxPool2D, UpSampling2D
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.losses import categorical_crossentropy
from tensorflow.keras.utils import Sequence
import cv2
directory = 'CaDIS' #Dossier dans lequel les images sont stockées
df_train = pd.read_csv('train.csv') #DataFrame avec informations sur les données d'entraînement
df_validation = pd.read_csv('val.csv') #DataFrame avec informations de données de validation
image_size = (224, 224) #Taille de l'image d'entrée
classes = 36 #Nombre de classes de classification
batch_size = 32 #Taille du lot
epochs = 300 #Nombre d'époques
loss = cce_dice_loss #Fonction de perte
optimizer = Adam(lr=0.001, amsgrad=True) #Fonction d'optimisation
metrics = dice_coeff #Méthode d'évaluation
#ImageDataGenerator Paramètres d'amplification d'image
aug_params = {'rotation_range': 5,
'width_shift_range': 0.05,
'height_shift_range': 0.05,
'shear_range': 0.1,
'zoom_range': 0.05,
'horizontal_flip': True,
'vertical_flip': True}
# val_Enregistrer le modèle uniquement lorsque la perte est minimisée
mc_cb = ModelCheckpoint('model_weights.h5',
monitor='val_loss', verbose=1,
save_best_only=True, mode='min')
#Lorsque l'apprentissage stagne, le taux d'apprentissage est de 0.Double
rl_cb = ReduceLROnPlateau(monitor='loss', factor=0.2, patience=3,
verbose=1, mode='auto',
min_delta=0.0001, cooldown=0, min_lr=0)
#Si l'apprentissage ne progresse pas, l'apprentissage sera interrompu de force
es_cb = EarlyStopping(monitor='loss', min_delta=0,
patience=5, verbose=1, mode='auto')
pour l'expansion des données. De plus, cette fois, nous utiliserons
Sequence` pour créer des données de mini-lots.__getitem__
est la partie qui crée spécifiquement un mini-lot. L'image d'entrée est traitée selon la procédure suivante.
#Générateur de données
@dataclasses.dataclass
class TrainSequence(Sequence):
directory: str #Dossier dans lequel les images sont stockées
df: pd.DataFrame #DataFrame avec informations sur les données
image_size: tuple #Taille de l'image d'entrée
classes: int #Nombre de classes de classification
batch_size: int #Taille du lot
aug_params: dict #ImageDataGenerator Paramètres d'amplification d'image
def __post_init__(self):
self.df_index = list(self.df.index)
self.train_datagen = ImageDataGenerator(**self.aug_params)
def __len__(self):
return math.ceil(len(self.df_index) / self.batch_size)
def __getitem__(self, idx):
batch_x = self.df_index[idx * self.batch_size:(idx+1) * self.batch_size]
x = []
y = []
for i in batch_x:
rand = np.random.randint(0, int(1e9))
#Image d'entrée
img = cv2.imread(f'{self.directory}/{self.df.at[i, "filename"]}')
img = cv2.resize(img, self.image_size, interpolation=cv2.INTER_LANCZOS4)
img = np.array(img, dtype=np.float32)
img = self.train_datagen.random_transform(img, seed=rand)
img *= 1./255
x.append(img)
#Image de segmentation
img = cv2.imread(f'{self.directory}/{self.df.at[i, "label"]}', cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img, self.image_size, interpolation=cv2.INTER_LANCZOS4)
img = np.array(img, dtype=np.float32)
img = np.reshape(img, (self.image_size[0], self.image_size[1], 1))
img = self.train_datagen.random_transform(img, seed=rand)
img = np.reshape(img, (self.image_size[0], self.image_size[1]))
seg = []
for label in range(self.classes):
seg.append(img == label)
seg = np.array(seg, np.float32)
seg = seg.transpose(1, 2, 0)
y.append(seg)
x = np.array(x)
y = np.array(y)
return x, y
#Générer un générateur
##Générateur de données d'entraînement
train_generator = TrainSequence(directory=directory, df=df_train,
image_size=image_size, classes=classes,
batch_size=batch_size, aug_params=aug_params)
step_size_train = len(train_generator)
##Générateur de données de validation
validation_generator = TrainSequence(directory=directory, df=df_validation,
image_size=image_size, classes=classes,
batch_size=batch_size, aug_params={})
step_size_validation = len(validation_generator)
# SegNet(8 couches d'encodeur, 8 couches de décodeur)Construire
def cnn(input_shape, classes):
#La taille de l'image d'entrée doit être un multiple de 32
assert input_shape[0]%32 == 0, 'Input size must be a multiple of 32.'
assert input_shape[1]%32 == 0, 'Input size must be a multiple of 32.'
#encodeur
##Couche d'entrée
inputs = Input(shape=(input_shape[0], input_shape[1], 3))
##1ère couche
x = Conv2D(32, (3, 3), padding='same', kernel_initializer='he_normal')(inputs)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
##2ème couche
x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
##3e couche
x = Conv2D(128, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
##4ème couche
x = Conv2D(256, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
##5ème et 6ème couches
x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
##7ème et 8ème couches
x = Conv2D(1024, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = Conv2D(1024, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
#Décodeur
##1ère couche
x = Conv2D(1024, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
##2ème et 3ème couches
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
##4ème couche
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(256, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
##5ème couche
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(128, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
##6ème couche
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
##7ème et 8ème couches
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
x = Conv2D(classes, (1, 1), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
outputs = Activation('softmax')(x)
return Model(inputs=inputs, outputs=outputs)
#Construction de réseau
model = cnn(image_size, classes)
model.summary()
model.compile(loss=loss, optimizer=optimizer, metrics=[metrics])
#Apprentissage
history = model.fit_generator(
train_generator, steps_per_epoch=step_size_train,
epochs=epochs, verbose=1, callbacks=[mc_cb, rl_cb, es_cb],
validation_data=validation_generator,
validation_steps=step_size_validation,
workers=3)
#Dessinez et enregistrez un graphique de la courbe d'apprentissage
def plot_history(history):
fig, (axL, axR) = plt.subplots(ncols=2, figsize=(10, 4))
# [la gauche]Graphique sur les métriques
L_title = 'Dice_coeff_vs_Epoch'
axL.plot(history.history['dice_coeff'])
axL.plot(history.history['val_dice_coeff'])
axL.grid(True)
axL.set_title(L_title)
axL.set_ylabel('dice_coeff')
axL.set_xlabel('epoch')
axL.legend(['train', 'test'], loc='upper left')
# [Côté droit]Graphique sur la perte
R_title = "Loss_vs_Epoch"
axR.plot(history.history['loss'])
axR.plot(history.history['val_loss'])
axR.grid(True)
axR.set_title(R_title)
axR.set_ylabel('loss')
axR.set_xlabel('epoch')
axR.legend(['train', 'test'], loc='upper left')
#Enregistrer le graphique en tant qu'image
fig.savefig('history.jpg')
plt.close()
#Sauvegarder la courbe d'apprentissage
plot_history(history)
6. Évaluation
from collections import defaultdict
directory = 'CaDIS' #Dossier dans lequel les images sont stockées
df_test = pd.read_csv('test.csv') #DataFrame avec informations sur les données de test
image_size = (224, 224) #Taille de l'image d'entrée
classes = 36 #Nombre de classes de classification
#Construction de réseau
model = cnn(image_size, classes)
model.summary()
model.load_weights('model_weights.h5')
#inférence
dict_iou = defaultdict(list)
for i in tqdm(range(len(df_test)), desc='predict'):
img = cv2.imread(f'{directory}/{df_test.at[i, "filename"]}')
height, width = img.shape[:2]
img = cv2.resize(img, image_size, interpolation=cv2.INTER_LANCZOS4)
img = np.array(img, dtype=np.float32)
img *= 1./255
img = np.expand_dims(img, axis=0)
label = cv2.imread(f'{directory}/{df_test.at[i, "label"]}', cv2.IMREAD_GRAYSCALE)
pred = model.predict(img)[0]
pred = cv2.resize(pred, (width, height), interpolation=cv2.INTER_LANCZOS4)
##Calcul IoU
pred = np.argmax(pred, axis=2)
for j in range(classes):
y_pred = np.array(pred == j, dtype=np.int)
y_true = np.array(label == j, dtype=np.int)
tp = sum(sum(np.logical_and(y_pred, y_true)))
other = sum(sum(np.logical_or(y_pred, y_true)))
if other != 0:
dict_iou[j].append(tp/other)
# average IoU
for i in range(classes):
if i in dict_iou:
dict_iou[i] = sum(dict_iou[i]) / len(dict_iou[i])
else:
dict_iou[i] = -1
print('average IoU', dict_iou)
Index
Class
average IoU[%]
0
Pupil
85.3
1
Surgical Tape
53.3
2
Hand
6.57
3
Eye Retractors
21.9
4
Iris
74.4
5
Eyelid
0.0
6
Skin
49.7
7
Cornea
88.0
8
Hydro. Cannula
0
9
Visco. Cannula
0
10
Cap. Cystotome
0
11
Rycroft Cannula
0
12
Bonn Forceps
3.58
13
Primary Knife
5.35
14
Phaco. Handpiece
0.0781
15
Lens Injector
16.4
16
A/I Handpiece
16.4
17
Secondary Knife
6.08
18
Micromanipulator
0
19
A/I Handpiece Handle
6.49
20
Cap. Forceps
0
21
Rycroft Cannula Handle
0
22
Phaco. Handpiece Handle
0
23
Cap. Cystotome Handle
0
24
Secondary Knife Handle
2.49
25
Lens Injector Handle
0
26
Water Sprayer
─
27
Suture Needle
0
28
Needle Holder
─
29
Charleux Cannula
0
30
Vannas Scissors
─
31
Primary Knife Handle
0
32
Viter. Handpiece
0
33
Mendez Ring
─
34
Biomarker
─
35
Marker
─
7. Résumé