In [1]:
import os
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from PIL import Image
import random
from scipy import ndarray
import skimage as sk
from skimage import transform
from skimage import util
from skimage import io
A seguir estão implementadas algumas funções que podem ser usadas para aplicação da técnica de Data Augmentation. São elas:
- Rotação de um ângulo aleatório entre 90 graus para direita e 90 graus para a esquerda.
- Adição de ruído gaussiano
- Adição de ruído sal & pimenta
- Reflexão horizontal
- Reflexão vertical
- Redução da resolução espacial
- Adição de um valor aleatório entre 10 e 50 em um dos canais da imagem RGB
- Cortes de regiões das imagens
- Translação horizontal
- Translação vertical
In [3]:
def random_rotation(image_array):
# pick a random degree of rotation between 90 on the left and 90 on the right
random_degree = random.uniform(-90, 90)
return sk.transform.rotate(image_array, random_degree)
def random_noise(image_array: ndarray):
image = np.copy(image_array)
if image.min() < 0:
low_clip = -1.
else:
low_clip = 0.
image = sk.img_as_float(image)
mean = 0.
var = 0.01
# add random noise to the image
noise = np.random.normal(mean, var ** 0.5, image.shape)
out = image + noise
out = np.clip(out, low_clip, 1.0)
return out
def salt_and_pepper_noise(image_array: ndarray):
if image_array.ndim == 2:
row,col = image_array.shape
else:
row,col,ch = image_array.shape
# add a salt and pepper noise
s_vs_p = 0.5
amount = 0.004
out = np.copy(image_array)
# Salt mode
num_salt = np.ceil(amount * image_array.size * s_vs_p)
coords_y = [np.random.randint(0, row, int(num_salt))]
coords_x = [np.random.randint(0, col, int(num_salt))]
out[coords_y, coords_x] = 255
# Pepper mode
num_pepper = np.ceil(amount* image_array.size * (1. - s_vs_p))
coords_y = [np.random.randint(0, row, int(num_pepper))]
coords_x = [np.random.randint(0, col, int(num_pepper))]
out[coords_y, coords_x] = 0
return out
def horizontal_flip(image_array: ndarray):
# horizontal flip doesn't need skimage, it's easy as flipping the image array of pixels !
return image_array[:, ::-1]
def vertical_flip(image_array: ndarray):
# vertical flip doesn't need skimage, it's easy as flipping the image array of pixels !
return image_array[::-1, :]
def rescale(image_array: ndarray):
img = np.copy(image_array)
# rescale the image by a factor
random.seed()
random_factor = random.choice([2, 4, 8])
return img[::random_factor,::random_factor]
def intensity_color(image_array: ndarray):
img = np.copy(image_array)
if image_array.ndim == 3:
random_ch = random.randint(0,3)
random_value = random.randint(10, 50)
img[:,:,random_ch] = img[:,:,random_ch] + random_value
else:
random_value = random.randint(10, 50)
img[:,:] = img[:,:] + random_value
return img
def crop2(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
return img[new_x:,new_y:]
def crop2_left_top(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
return img[new_x:,:new_y]
def crop2_down(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
return img[:new_x,new_y:]
def crop2_left_down(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
return img[:new_x, :new_y]
def crop2_center(image_array: ndarray,cropx,cropy):
if image_array.ndim == 2:
y,x = image_array.shape
else:
y,x, z = image_array.shape
startx = x//2-(cropx//2)
starty = y//2-(cropy//2)
img = np.copy(image_array)
return img[starty:starty+cropy,startx:startx+cropx]
def crop(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
img[:new_x,:] = 0
img[:,:new_y] = 0
return img
def crop_left_top(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
img[:new_x,:] = 0
img[:,new_y:] = 0
return img
def crop_down(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
img[new_x:,:] = 0
img[:,:new_y] = 0
return img
def crop_left_down(image_array: ndarray, new_x, new_y):
img = np.copy(image_array)
img[new_x:,:] = 0
img[:,new_y:] = 0
return img
def crop_center(image_array: ndarray,cropx,cropy):
if image_array.ndim == 2:
y,x = image_array.shape
else:
y,x, z = image_array.shape
startx = x//2-(cropx//2)
starty = y//2-(cropy//2)
img = np.copy(image_array)
img[starty:starty+cropy,startx:startx+cropx]
img[:starty,:] = 0
img[:,:startx] = 0
img[starty+cropy:,:] = 0
img[:,startx+cropx:] = 0
return img
def x_trans_slice(img_slice, x_vox_trans):
# Make a 0-filled array of same shape as `img_slice`
trans_slice = np.zeros(img_slice.shape)
# Use slicing to select voxels out of the image and move them
# up or down on the first (x) axis
if x_vox_trans < 0:
if img_slice.ndim == 2:
trans_slice[:x_vox_trans, :] = img_slice[-x_vox_trans:, :]
else:
trans_slice[:x_vox_trans, :, 0] = img_slice[-x_vox_trans:, :, 0]
trans_slice[:x_vox_trans, :, 1] = img_slice[-x_vox_trans:, :, 1]
trans_slice[:x_vox_trans, :, 2] = img_slice[-x_vox_trans:, :, 2]
elif x_vox_trans == 0:
trans_slice[:, :] = img_slice
else:
if img_slice.ndim == 2:
trans_slice[x_vox_trans:, :] = img_slice[:-x_vox_trans, :]
else:
trans_slice[x_vox_trans:, :, 0] = img_slice[:-x_vox_trans, :, 0]
trans_slice[x_vox_trans:, :, 1] = img_slice[:-x_vox_trans, :, 1]
trans_slice[x_vox_trans:, :, 2] = img_slice[:-x_vox_trans, :, 2]
return trans_slice.astype('uint8')
def y_trans_slice(img_slice, y_vox_trans):
# Make a 0-filled array of same shape as `img_slice`
trans_slice = np.zeros(img_slice.shape)
# Use slicing to select voxels out of the image and move them
# up or down on the first (x) axis
if y_vox_trans < 0:
if img_slice.ndim == 2:
trans_slice[:, :y_vox_trans] = img_slice[:,-y_vox_trans:]
else:
trans_slice[:, :y_vox_trans, 0] = img_slice[:,-y_vox_trans:, 0]
trans_slice[:, :y_vox_trans, 1] = img_slice[:,-y_vox_trans:, 1]
trans_slice[:, :y_vox_trans, 2] = img_slice[:,-y_vox_trans:, 2]
elif y_vox_trans == 0:
trans_slice[:, :] = img_slice
else:
if img_slice.ndim == 2:
trans_slice[:,y_vox_trans:] = img_slice[:, :-y_vox_trans]
else:
trans_slice[:,y_vox_trans:, 0] = img_slice[:, :-y_vox_trans, 0]
trans_slice[:,y_vox_trans:, 1] = img_slice[:, :-y_vox_trans, 1]
trans_slice[:,y_vox_trans:, 2] = img_slice[:, :-y_vox_trans, 2]
return trans_slice.astype('uint8')
# dictionary of the transformations functions we defined earlier
available_transformations = {
'rotate': random_rotation,
'noise': random_noise,
'salt_and_pepper_noise': salt_and_pepper_noise,
'horizontal_flip': horizontal_flip,
'vertical_flip': vertical_flip,
'rescale': rescale,
'crop': crop,
'crop_left_top': crop_left_top,
'crop_down': crop_down,
'crop_left_down': crop_left_down,
'crop_center': crop_center,
'crop2': crop2,
'crop2_left_top': crop2_left_top,
'crop2_down': crop2_down,
'crop2_left_down': crop2_left_down,
'crop2_center': crop2_center,
'intensity_color': intensity_color,
'x_trans_slice': x_trans_slice,
'y_trans_slice': y_trans_slice
}
f = Image.open('176.jpg')
f = np.array(f)
In [4]:
key = 'rotate'
transformed_image1 = available_transformations[key](f)
transformed_image2 = available_transformations[key](f)
transformed_image3 = available_transformations[key](f)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(1, 3, 1)
plt.imshow(transformed_image1)
fig.add_subplot(1, 3, 2)
plt.imshow(transformed_image2)
fig.add_subplot(1, 3, 3)
plt.imshow(transformed_image3)
plt.show()
A célula abaixo contém a imagem original, a imagem refletida horizontalmente e, em seguida, verticalmente.
In [4]:
key = 'horizontal_flip'
transformed_imageHF = available_transformations[key](f)
key = 'vertical_flip'
transformed_imageVF = available_transformations[key](f)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(1, 3, 1)
plt.imshow(f, cmap='gray')
fig.add_subplot(1, 3, 2)
plt.imshow(transformed_imageHF, cmap='gray')
fig.add_subplot(1, 3, 3)
plt.imshow(transformed_imageVF, cmap='gray')
plt.show()
A célula abaixo testa a função de redução da resolução espacial da imagem. A função escolhe de maneira aleatória se irá diminuir pela metade, um quarto ou um oitavo a quantidade de pixels da imagem. O resultado desse teste mostra primeiro a imagem original, e depois duas chamadas da função.
In [22]:
key = 'rescale'
transformed_image4 = available_transformations[key](f)
transformed_image5 = available_transformations[key](f)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(1, 3, 1)
plt.imshow(f)
fig.add_subplot(1, 3, 2)
plt.imshow(transformed_image4)
fig.add_subplot(1, 3, 3)
plt.imshow(transformed_image5)
plt.show()
A célula abaixo mostra o teste com as funções de cortes de regiões da imagem. As imagens representam cortes nas regiões inferiror esquerda, superior esquerda, inferior direita, superior direita e centro da imagem.
In [6]:
key = 'crop_left_top'
transformed_image6 = available_transformations[key](f, 75, 100)
key = 'crop_left_down'
transformed_image7 = available_transformations[key](f, 140, 140)
key = 'crop'
transformed_image8 = available_transformations[key](f, 50, 50)
key = 'crop_down'
transformed_image9 = available_transformations[key](f, 150, 50)
key = 'crop_center'
transformed_image10 = available_transformations[key](f, 150, 150)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(2, 3, 1)
plt.imshow(transformed_image6)
fig.add_subplot(2, 3, 2)
plt.imshow(transformed_image7)
fig.add_subplot(2, 3, 3)
plt.imshow(transformed_image8)
fig.add_subplot(2, 3, 4)
plt.imshow(transformed_image9)
fig.add_subplot(2, 3, 5)
plt.imshow(transformed_image10)
plt.show()
Nessa segunda versão da função de cortes, a imagem retornada não contém o mesmo tamanho da imagem original, por isso não possui as regiões em preto. Entretanto, as imagens serão de tamanhos variados, dependendo da região do corte que os parâmetros da função definem.
In [7]:
key = 'crop2_left_top'
transformed_image11 = available_transformations[key](f, 100, 170)
key = 'crop2_left_down'
transformed_image12 = available_transformations[key](f, 140, 140)
key = 'crop2'
transformed_image13 = available_transformations[key](f, 70, 70)
key = 'crop2_down'
transformed_image14 = available_transformations[key](f, 150, 50)
key = 'crop2_center'
transformed_image15 = available_transformations[key](f, 150, 150)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(2, 3, 1)
plt.imshow(transformed_image11)
fig.add_subplot(2, 3, 2)
plt.imshow(transformed_image12)
fig.add_subplot(2, 3, 3)
plt.imshow(transformed_image13)
fig.add_subplot(2, 3, 4)
plt.imshow(transformed_image14)
fig.add_subplot(2, 3, 5)
plt.imshow(transformed_image15)
plt.show()
A célula a seguir testa a função de translação vertical, horizontal e a combinação das duas, ou seja, aplicando uma translação e seu resultado à outra. Os parâmetros dizem respeito ao deslocamento que os pixels da imagem original devem ser submetidos.
In [24]:
fig=plt.figure(figsize=(10, 18))
fig.add_subplot(3, 2, 1)
plt.imshow(x_trans_slice(f, 100))
fig.add_subplot(3, 2, 2)
plt.imshow(x_trans_slice(f, -100))
fig.add_subplot(3, 2, 3)
plt.imshow(y_trans_slice(f, 100))
fig.add_subplot(3, 2, 4)
plt.imshow(y_trans_slice(f, -100))
fig.add_subplot(3, 2, 5)
plt.imshow(x_trans_slice(y_trans_slice(f, 75), 75))
fig.add_subplot(3, 2, 6)
plt.imshow(x_trans_slice(y_trans_slice(f, 75), -75))
plt.show()
In [8]:
key = 'noise'
transformed_image11 = available_transformations[key](f)
key = 'salt_and_pepper_noise'
transformed_image12 = available_transformations[key](f)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(1, 3, 1)
plt.imshow(transformed_image11)
fig.add_subplot(1, 3, 2)
plt.imshow(transformed_image12)
plt.show()
In [9]:
key = 'intensity_color'
transformed_image11 = available_transformations[key](f)
key = 'intensity_color'
transformed_image12 = available_transformations[key](f)
fig=plt.figure(figsize=(18, 10))
fig.add_subplot(1, 3, 1)
plt.imshow(f)
fig.add_subplot(1, 3, 2)
plt.imshow(transformed_image11)
fig.add_subplot(1, 3, 3)
plt.imshow(transformed_image12)
plt.show()
Assim como já apresentado na função de translação, é possível combinar as transformações, de modo a gerar uma quantidade ainda maior de imagens tecnicamente diferentes, derivadas de um mesmo conjunto de dados.
O teste a seguir consiste em aplicar uma translação horizontal, seu resultado na translação vertical e por fim, aplicado à reflexão horizontal.
In [28]:
translated_x_y = x_trans_slice(y_trans_slice(f, 75), -75)
key = 'horizontal_flip'
transformed_imageVF = available_transformations[key](translated_x_y)
fig=plt.figure(figsize=(18, 12))
fig.add_subplot(1, 4, 1)
plt.imshow(f)
fig.add_subplot(1, 4, 2)
plt.imshow(y_trans_slice(f, 75))
fig.add_subplot(1, 4, 3)
plt.imshow(translated_x_y)
fig.add_subplot(1, 4, 4)
plt.imshow(transformed_imageVF)
plt.show()
A seguir, foi criado um cógido que gera automaticamente um número pré determinado de imagens artificiais. Ele escolhe de maneira aleatória uma das funções disponíveis de Data Augmentation. Portanto, foram selecionadas apenas as funções não parametrizadas das apresentadas acima. É preciso ter uma base de imagens inicial e após o programa ler essas imagens, ele escolhe aleatoriamente uma para realizar uma transformação. Por fim, ele salva a imagem, nomeando-a como "augmented_image + o nome da imagem original" e o critério de parada é o número de imagens desejadas ser alcançado.
In [29]:
# dictionary of the transformations we defined earlier
available_transformations = {
'rotate': random_rotation,
'noise': random_noise,
'horizontal_flip': horizontal_flip,
'vertical_flip': vertical_flip,
'salt_and_pepper_noise': salt_and_pepper_noise,
'rescale': rescale
}
folder_path = '../../lion'
num_files_desired = 20
# find all files paths from the folder
images = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
num_generated_files = 0
while num_generated_files <= num_files_desired:
# random image from the folder
image_path = random.choice(images)
# read image as an two dimensional array of pixels
image_to_transform = Image.open(image_path)
image_to_transform = np.array(image_to_transform)
# random num of transformation to apply
num_transformations_to_apply = random.randint(1, len(available_transformations))
num_transformations = 0
transformed_image = None
while num_transformations <= num_transformations_to_apply:
# random transformation to apply for a single image
key = random.choice(list(available_transformations))
transformed_image = available_transformations[key](image_to_transform)
num_transformations += 1
#save images
#define a name for our new file
new_file_path = '%s/augmented_image_%s.jpg' % (folder_path, num_generated_files)
# write image to the disk
sk.io.imsave(new_file_path, transformed_image)
num_generated_files += 1