[PYTHON] J'ai essayé de traiter et de transformer l'image et d'élargir les données pour l'apprentissage automatique

Aperçu

L'apprentissage en profondeur nécessite une grande quantité de données pour être préparé à un apprentissage efficace. Cependant, lorsqu'il est difficile de préparer une grande quantité de données, une technique appelée Data Augmentation peut être utilisée pour gonfler (proliférer) une petite quantité de données disponibles et l'utiliser pour la formation.

Lorsque les données sont "image", ** mouvement parallèle **, ** rotation **, ** agrandissement / réduction **, ** à l'envers **, ** inversion gauche / droite **, ** réglage de la luminosité comme suit Les données sont étendues par un traitement d'image qui combine la déformation et le traitement tel que **. f1ex_arr.png

Dans l'environnement de Tensorflow (2.x) + Keras, une classe appelée ʻImageDataGenerator est préparée pour l'expansion des données, et si vous l'utilisez, vous pouvez utiliser ** les données avec un traitement d'image aléatoire (cela est étendu dans cet article. Il est relativement facile de générer ** une image). Cet article décrit l'expansion des données avec ce ʻImageDataGenerator.

De plus, sans utiliser ʻImageDataGenerator, des bibliothèques telles que ** OpenCV ** et ** tf.keras ** ** affine transforms ** (cv2.warpAffine et tensorflow.keras.preprocessing.image.apply_affine_transform " Utilisez `) pour effectuer ** un mouvement, une rotation et une mise à l'échelle parallèles ** pour tenter une expansion manuelle des données. Comparez également les vitesses de traitement des deux bibliothèques (le résultat est une victoire écrasante pour OpenCV).

Environnement d'exécution

Nous vérifions l'exécution dans l'environnement Google Colab.

opencv-python      4.1.2.30
tensorflow         2.1.0rc1

Extension des données d'image avec ImageDataGenerator

Préparation: Importer la bibliothèque

Changez la version de Tesorflow et importez la bibliothèque. Il utilise également matplotlib pour voir les images avant et après le traitement, alors importez-les également.

Préparation: Importer la bibliothèque


%tensorflow_version 2.x
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

Préparation: préparation des données d'image

Acquérir les données cibles pour le traitement. Ici, nous utiliserons les données d'entraînement de "** CIFAR-10 **". CIFAR-10 est un ensemble de données composé de 10 types d'images: «avion», «automobile», «oiseau», «chat», «cerf», «chien», «grenouille», «cheval», «bateau» et «camion».

Préparation: préparation des données d'image


(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
img_cifar10 = x_train/255.
print(img_cifar10.shape) # -> (50000, 32, 32, 3)  32x32 3ch RGB 

ʻImg_cifar10` contient $ 3 $ ch-RGB $ 50 000 $ images de 32 $ \ fois 32 $ px au format numpy.ndarray.

Préparation: Définition de la fonction d'affichage d'image

Créez une fonction pour afficher l'image. Définissez showImage (...) pour afficher une seule image et showImageArray (...) pour afficher plusieurs images en même temps en donnant un tableau d'images.

Préparation: Définition de la fonction d'affichage d'image


def showImage(img,title=None):
  plt.figure(figsize=(3, 3))
  plt.gcf().patch.set_facecolor('white')
  plt.xticks([])
  plt.yticks([])
  plt.title(title)
  plt.imshow(img)
  plt.show()
  plt.close()

def showImageArray(img_arry):
  n_cols = 8
  n_rows = ((len(img_arry)-1)//n_cols)+1
  fig, ax = plt.subplots(nrows=n_rows, ncols=n_cols, figsize=(10, 1.25*n_rows))
  fig.patch.set_facecolor('white')
  for i,ax in enumerate( ax.flatten() ):
    if i < len(img_arry):
      ax.imshow(img_arry[i])
      ax.set_xticks([])
      ax.set_yticks([])
    else :
      ax.axis('off') #Traitement des marges
  plt.show()
  plt.close()

Appelez ces fonctions comme suit:

Appel d'une fonction d'affichage d'image


# img_Afficher la 12ème image de cifar10
showImage(img_cifar10[12],title='CIFAR-10 Train Data [12]')

#Images du 0 au 39 (=40 feuilles) s'affiche
showImageArray(img_cifar10[:40])

Pour showImage (...), donnez numpy.ndarray avec la forme (32,32,3) comme argument. Aussi, pour showImageArray (...), donnez numpy.ndarray avec la forme (arr_len, 32,32,3) comme argument (où ʻarr_len` est la longueur du tableau d'image).

Le résultat de l'exécution est le suivant. f1.png f2.png

Extension des données d'image avec ImageDataGenerator

Classe ImageDataGenerator Peut charger des fichiers image dans le répertoire spécifié et développer les données. Ici, il est utilisé à des fins d'expansion des données.

Lorsqu'il est utilisé pour l'expansion des données, donnez le paramètre "** Quel type de traitement (mouvement, rotation, agrandissement?) Est effectué aléatoirement à quelle intensité **" au moment de l'initialisation. Par exemple, initialisez-le en donnant les paramètres suivants.

Initialisation de ImageDataGenerator


ImageDataGenerator = tf.keras.preprocessing.image.ImageDataGenerator
image_data_generator = ImageDataGenerator(
  rotation_range=20,       #Rotation aléatoire de ± 20 degrés
  width_shift_range=8,     #Déplacement aléatoire à gauche et à droite dans une plage de ± 8px
  height_shift_range=4,    #Déplacement aléatoire de haut en bas dans une plage de ± 4 px
  zoom_range=(0.8, 1.2),   #Aléatoire 0.8~1.Zoom avant 2x
  horizontal_flip=True,    #Retournez au hasard à gauche et à droite
  channel_shift_range=0.2) #Plage de décalage aléatoire de la valeur du canal (luminosité)

Étant donné width_shift_range et height_shift_range comme des valeurs décimales inférieures à 1 $, vous pouvez spécifier une plage aléatoire sous forme de pourcentage de la taille de l'image. De plus, bien que non utilisé cette fois, vous pouvez ajouter un traitement à l'envers avec vertical_flip = True.

Générer une seule image étendue

En utilisant ce générateur, je vais essayer d'élargir les données de l'image "cheval" de ʻimg_cifar10 [12] `(D'abord, ** une seule ** image étendue sera générée). Lorsque vous exécutez le code suivant, le traitement de l'image sera effectué de manière aléatoire dans la plage de paramètres donnée dans l'initialisation ci-dessus.

Générer une seule image étendue


org_img = img_cifar10[12].copy() #La cible est le 12e "cheval"
ex_img  = image_data_generator.flow( org_img.reshape(1,32,32,3), batch_size=1)[0][0]
print(ex_img.shape) # -> (32, 32, 3)
showImage(ex_img,title='CIFAR-10 Train Data [12] Ex')

Utilisez la méthode flow (...) pour obtenir l'image étendue. En argument, donnez l'image d'origine ** tableau ** (converti en un tableau avec un élément avec .reshape (1,32,32,3) car il ne prend pas en charge une seule entrée). La valeur de retour sera NumpyArrayIterator, donc évaluez avec [0] [0] et le 0e élément. Est acquis et stocké dans ʻex_img`.

Le résultat de l'exécution est le suivant. La gauche et la droite sont inversées, un mouvement horizontal est appliqué et il est globalement plus clair (les résultats de l'exécution changent à chaque exécution). f1ex.png

À propos, si vous faites pivoter l'image ou la déplacez vers le haut / le bas / la gauche / la droite, un espace sera créé, mais le bord de l'image sera automatiquement étiré et rempli (le résultat sera une image naturelle). Si vous ne souhaitez pas que cela soit appliqué, spécifiez fill_mode = 'constant' dans l'initialisation. Ensuite, les marges sont remplies de noir comme indiqué ci-dessous. De plus, vous pouvez spécifier fill_mode = 'reflect', fill_mode =' wrap' et fill_mode = 'le plus proche' (par défaut). f1ex2.png

Générer plusieurs images étendues

Ensuite, 39 images étendues sont générées à partir de l'image "horse" de ʻimg_cifar10 [12] . ʻImage_data_generator.flow (...) renvoie NumpyArrayIterator, donc utiliseznext ()pour récupérer les images séquentiellement.

Générer une ou plusieurs images étendues


org_img = img_cifar10[12].copy()
ex_img = np.empty([40, 32, 32, 3]) #Préparez une zone pouvant stocker 40 feuilles, y compris l'original
ex_img[0,:,:,:] = org_img          #Stockez l'original sur la 0ème feuille
iter_ = image_data_generator.flow( org_img.reshape(1,32,32,3), batch_size=1)
for i in range(1,40):
  ex_img[i,:,:,:] = iter_.next()[0] #Stocker séquentiellement les images générées
showImageArray(ex_img)

Le résultat de l'exécution est le suivant. f1ex_arr.png

Générer une image étendue pour chacune des 0e à 23e images de CIFAR-10

Dans la section précédente, nous avons généré plusieurs images étendues pour une image. Cette fois, pour chacune des 40 images du 0 au 23 des données d'apprentissage du CIFAR-10, une image étendue est générée.

Générer une image étendue pour chaque tableau d'images


showImageArray(img_cifar10[:24]) # CIFAR-10 Afficher les 0e à 23e feuilles originales

ex_img = np.empty([24, 32, 32, 3])
ex_img = image_data_generator.flow(img_cifar10[:24], batch_size=24, shuffle=False)[0]
showImageArray(ex_img)           # CIFAR-10 Expand 0 à 23 s'affiche

Le résultat de l'exécution est le suivant. Tout d'abord, voici les données originales du 0e au 23e données d'entraînement du CIFAR-10. cifar10.png

Ensuite, voici l'image étendue avec ImageDataGenerator. Vous pouvez voir que différents traitements (combinaison de mouvement, rotation, mise à l'échelle, retournement, etc.) sont appliqués à chaque image. cifar10ex.png

Si vous ne spécifiez pas shuffle = False dans l'argument de flow (...), l'ordre des séquences d'images de sortie sera mélangé.

Comparaison des traitements par OpenCV et tf.keras

Spécifiez manuellement la quantité de mouvement, l'angle de rotation et le grossissement, et générez des images étendues avec les bibliothèques OpenCV et tf.keras. Comparez également le temps de traitement.

rotation

L'image est ** tournée dans le sens antihoraire </ font> 45 degrés **. L'axe de rotation est le centre de l'image.

Un programme qui utilise la bibliothèque OpenCV ressemble à ceci: Dans OpenCV, le sens antihoraire est "positif", donc l'angle de rotation "45" est donné tel quel. cv2.warpAffine (...) est le corps du processus. Si vous ne spécifiez pas borderMode = cv2.BORDER_REPLICATE, l'espace sera rempli de noir.

Version OpenCV de CIFAR10 50,Traitement de rotation de 000 feuilles


import time
import cv2
deg = 45  #Le sens antihoraire est "positif"
w, h = 32, 32  #Taille de l'image
m = cv2.getRotationMatrix2D((w/2,h/2), deg, 1) #Matrice de transformation
img_cifar10_ex = np.empty_like(img_cifar10) #Emplacement de stockage des résultats np.empty([50000,32,32,3])Pareil que
t1 = time.time()
for i,img in enumerate( img_cifar10 ) :
  img = cv2.warpAffine(img, m, (w,h), borderMode=cv2.BORDER_REPLICATE)
  img_cifar10_ex[i,:,:,:] = img
t2 = time.time()
print(f'temps de traitement{t2-t1:.1f}[sec]')
showImageArray(img_cifar10_ex[:24]) #Afficher seulement 24 feuilles

Le résultat de l'exécution est le suivant. En outre, le temps de traitement était de ** 1,3 [s] **. cv_r.png

D'un autre côté, un programme qui utilise la bibliothèque tf.keras ressemble à ceci: Ici, le sens antihoraire est "négatif", donc l'angle de rotation est donné comme "-45".

tf.50 de la version keras de CIFAR10,Traitement de rotation de 000 feuilles


import time
from tensorflow.keras.preprocessing.image import apply_affine_transform
deg = -45  #Le sens antihoraire est "négatif"
img_cifar10_ex = np.empty_like(img_cifar10) #Emplacement de stockage des résultats
t1 = time.time()
for i,img in enumerate( img_cifar10 ) :
  img = apply_affine_transform(img, theta=deg)
  img_cifar10_ex[i,:,:,:] = img
t2 = time.time()
print(f'temps de traitement{t2-t1:.1f}[sec]')
showImageArray(img_cifar10_ex[:24])

Le résultat de l'exécution est le suivant. En outre, le temps de traitement était de ** 16,2 [s] **. La vitesse de traitement était extrêmement ** plus rapide ** avec OpenCV </ font>. tf_r.png

Mouvement parallèle

Déplace l'image ** vers la droite ** de $ 2 $ px et ** vers le bas ** de $ 5 $ px.

La première est la version OpenCV. Le processus est le même que la "rotation" précédente, seul le contenu de la matrice de conversion "m" est différent.

Version OpenCV de CIFAR10 50,Déplacement de 000 feuilles


import time
import cv2
tx, ty = 2, 5 
w, h = 32, 32  #Taille de l'image
m = np.float32([[1,0,tx],[0,1,ty]]) #Matrice de transformation
img_cifar10_ex = np.empty_like(img_cifar10) #Emplacement de stockage des résultats
t1 = time.time()
for i,img in enumerate( img_cifar10 ) :
  img = cv2.warpAffine(img, m, (w,h), borderMode=cv2.BORDER_REPLICATE)
  img_cifar10_ex[i,:,:,:] = img
t2 = time.time()
print(f'temps de traitement{t2-t1:.1f}[sec]')
showImageArray(img_cifar10_ex[:24])

Le résultat de l'exécution est le suivant. Le temps de traitement était de ** 1,3 [sec] ** (puisque le traitement essentiel est le même que la "rotation", le temps d'exécution ne change pas). cv_m.png

Vient ensuite la version tf.keras. Je n'ai pas compris pourquoi il a été conçu comme ça, mais cela ressemble à ʻapply_affine_transform (img, tx = -5, ty = -2) `pour déplacer 2px vers la droite et 5px vers le bas. Doit être spécifié. Le mystère est.

--Référence: [La méthode Keras ImageDataGenerator apply_transform () décale l'image dans la direction opposée](https://stackoverflow.com/questions/56580076/keras-imagedatagenerator-apply-transform-method-shifts-the-image-in-opposite- ré)

tf.50 de la version keras de CIFAR10,Traitement de rotation de 000 feuilles


import time
from tensorflow.keras.preprocessing.image import apply_affine_transform
tx, ty = 2, 5 
img_cifar10_ex = np.empty_like(img_cifar10) #Emplacement de stockage des résultats
t1 = time.time()
for i,img in enumerate( img_cifar10 ) :
  img = apply_affine_transform(img, tx=-ty, ty=-tx) #Faites attention à la façon de donner des arguments
  img_cifar10_ex[i,:,:,:] = img
t2 = time.time()
print(f'temps de traitement{t2-t1:.1f}[sec]')
showImageArray(img_cifar10_ex[:24])

Le résultat de l'exécution est le suivant. Le temps de traitement est de ** 13,9 [sec] **, ce qui est environ 10 fois plus long que le temps de traitement d'OpenCV comme auparavant. tf_m.png

Expansion

L'image est agrandie 1,2 fois (la taille de l'image ne change pas même si elle est agrandie).

La première est la version OpenCV. Agrandissez avec resize puis coupez à 32 $ \ fois 32 $ px.

Version OpenCV de CIFAR10 50,Traitement d'agrandissement de 000 feuilles


import cv2
f = 1.2 
w, h = 32, 32               #Taille de l'image
w2, h2 = int(w*f), int(h*f) #Taille d'image agrandie
tx, ty = int((w2-w)/2),int((h2-h)/2) #Couper les coordonnées de début
m = np.float32([[1,0,tx],[0,1,ty]]) #Matrice de transformation
img_cifar10_ex = np.empty_like(img_cifar10) #Emplacement de stockage des résultats
t1 = time.time()
for i,img in enumerate( img_cifar10 ) :
  img = cv2.resize(img,(w2,h2)) 
  img_cifar10_ex[i,:,:,:] = img[tx:tx+w,ty:ty+h,:] #Découpez 32 x 32 à partir d'une image agrandie
t2 = time.time()
print(f'temps de traitement{t2-t1:.1f}[sec]')
showImageArray(img_cifar10_ex[:24])

Le résultat de l'exécution est le suivant. Le temps de traitement était de ** 1,2 [s] **. cv_f.png

Vient ensuite la version tf.keras. C'est aussi très déroutant comme avant, mais comme ʻapply_affine_transform (img, zx = 1 / f, zy = 1 / f) , les arguments zx et zy` ont "** inverse du grossissement **". Est donnée.

tf.50 de la version keras de CIFAR10,Traitement d'agrandissement de 000 feuilles


import time
from tensorflow.keras.preprocessing.image import apply_affine_transform
f = 1.2
img_cifar10_ex = np.empty_like(img_cifar10) #Emplacement de stockage des résultats
t1 = time.time()
for i,img in enumerate( img_cifar10 ) :
  img = apply_affine_transform(img, zx=1/f, zy=1/f) #Attention à l'argument
  img_cifar10_ex[i,:,:,:] = img
t2 = time.time()
print(f'temps de traitement{t2-t1:.1f}[sec]')
showImageArray(img_cifar10_ex[:24])

Le temps de traitement est de 13,4 [s] et le résultat est le suivant. tf_f.png

Recommended Posts