""" Homework 5 - CNNs CS1430 - Computer Vision Brown University """ import os import random import numpy as np from PIL import Image import tensorflow as tf import hyperparameters as hp class Datasets(): """ Class for containing the training and test sets as well as other useful data-related information. Contains the functions for preprocessing. """ def __init__(self, data_path, task): self.data_path = data_path self.task = task # Dictionaries for (label index) <--> (class name) self.idx_to_class = {} self.class_to_idx = {} # For storing list of classes self.classes = [""] * hp.num_classes # Mean and std for standardization self.mean = np.zeros((3,)) self.std = np.zeros((3,)) self.calc_mean_and_std() # Setup data generators self.train_data = self.get_data( os.path.join(self.data_path, "train/"), task == '3', True, True) self.test_data = self.get_data( os.path.join(self.data_path, "test/"), task == '3', False, False) def calc_mean_and_std(self): """ Calculate mean and standard deviation of a sample of the training dataset for standardization. Arguments: none Returns: none """ # Get list of all images in training directory file_list = [] for root, _, files in os.walk(os.path.join(self.data_path, "train/")): for name in files: if name.endswith(".jpg"): file_list.append(os.path.join(root, name)) # Shuffle filepaths random.shuffle(file_list) # Take sample of file paths file_list = file_list[:hp.preprocess_sample_size] # Allocate space in memory for images data_sample = np.zeros( (hp.preprocess_sample_size, hp.img_size, hp.img_size, 3)) # Import images for i, file_path in enumerate(file_list): img = Image.open(file_path) img = img.resize((hp.img_size, hp.img_size)) img = np.array(img, dtype=np.float32) img /= 255. # Grayscale -> RGB if len(img.shape) == 2: img = np.stack([img, img, img], axis=-1) data_sample[i] = img # TODO: Calculate the pixel-wise mean and standard deviation # of the images in data_sample and store them in # self.mean and self.std respectively. # ========================================================== self.mean = np.mean(data_sample, axis=(1,2,3), dtype=np.float32) self.std = np.std(data_sample, axis=(1,2,3), dtype=np.float32) # ========================================================== print("Dataset mean: [{0:.4f}, {1:.4f}, {2:.4f}]".format( self.mean[0], self.mean[1], self.mean[2])) print("Dataset std: [{0:.4f}, {1:.4f}, {2:.4f}]".format( self.std[0], self.std[1], self.std[2])) def standardize(self, img): """ Function for applying standardization to an input image. Arguments: img - numpy array of shape (image size, image size, 3) Returns: img - numpy array of shape (image size, image size, 3) """ # TODO: Standardize the input image. Use self.mean and self.std # that were calculated in calc_mean_and_std() to perform # the standardization. # ============================================================= img = (img - np.max(self.mean))/np.max(self.std) # ============================================================= return img def preprocess_fn(self, img): """ Preprocess function for ImageDataGenerator. """ if self.task == '3': img = tf.keras.applications.vgg16.preprocess_input(img) else: img = img / 255. img = self.standardize(img) return img def custom_preprocess_fn(self, img): """ Custom preprocess function for ImageDataGenerator. """ if self.task == '3': img = tf.keras.applications.vgg16.preprocess_input(img) else: img = img / 255. img = self.standardize(img) # EXTRA CREDIT: # Write your own custom data augmentation procedure, creating # an effect that cannot be achieved using the arguments of # ImageDataGenerator. This can potentially boost your accuracy # in the validation set. Note that this augmentation should # only be applied to some input images, so make use of the # 'random' module to make sure this happens. Also, make sure # that ImageDataGenerator uses *this* function for preprocessing # on augmented data. if random.random() < 0.3: img = img + tf.random.uniform( (hp.img_size, hp.img_size, 1), minval=-0.1, maxval=0.1) return img def get_data(self, path, is_vgg, shuffle, augment): """ Returns an image data generator which can be iterated through for images and corresponding class labels. Arguments: path - Filepath of the data being imported, such as "../data/train" or "../data/test" is_vgg - Boolean value indicating whether VGG preprocessing should be applied to the images. shuffle - Boolean value indicating whether the data should be randomly shuffled. augment - Boolean value indicating whether the data should be augmented or not. Returns: An iterable image-batch generator """ if augment: # TODO: Use the arguments of ImageDataGenerator() # to augment the data. Leave the # preprocessing_function argument as is unless # you have written your own custom preprocessing # function (see custom_preprocess_fn()). # # Documentation for ImageDataGenerator: https://bit.ly/2wN2EmK # # ============================================================ # data_gen = data_gen = tf.keras.preprocessing.image.ImageDataGenerator( preprocessing_function=self.preprocess_fn, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True, validation_split=0.2, fill_mode="reflect") # ============================================================ else: # Don't modify this data_gen = tf.keras.preprocessing.image.ImageDataGenerator( preprocessing_function=self.preprocess_fn) # VGG must take images of size 224x224 img_size = 224 if is_vgg else hp.img_size classes_for_flow = None # Make sure all data generators are aligned in label indices if bool(self.idx_to_class): classes_for_flow = self.classes # Form image data generator from directory structure data_gen = data_gen.flow_from_directory( path, target_size=(img_size, img_size), class_mode='sparse', batch_size=hp.batch_size, shuffle=shuffle, classes=classes_for_flow) # Setup the dictionaries if not already done if not bool(self.idx_to_class): unordered_classes = [] for dir_name in os.listdir(path): if os.path.isdir(os.path.join(path, dir_name)): unordered_classes.append(dir_name) for img_class in unordered_classes: self.idx_to_class[data_gen.class_indices[img_class]] = img_class self.class_to_idx[img_class] = int(data_gen.class_indices[img_class]) self.classes[int(data_gen.class_indices[img_class])] = img_class return data_gen