Image augmentation reproducibility

212 views Asked by At

How can I reproduce the same image augmentation results when I'm using the following data pipeline for an image segmentation task? I want to compare different models, so data augmentation needs to be deterministic.

class data_loader(object):
    def __init__(self, image_paths: List[str], mask_paths: List[str],
        image_size: int=IMAGE_SIZE, augment: bool=True):

        self.image_paths = image_paths
        self.mask_paths = mask_paths
        self.image_size = image_size
        self.augment = augment

    def randomHueSaturationValue(self, image, hue_shift_limit=(-180, 180),
                             sat_shift_limit=(-255, 255),
                             val_shift_limit=(-255, 255), u=0.5):
        if np.random.random() < u:
            image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
            h, s, v = cv2.split(image)
            hue_shift = np.random.randint(hue_shift_limit[0], hue_shift_limit[1] + 1)
            hue_shift = np.uint8(hue_shift)
            h += hue_shift
            sat_shift = np.random.uniform(sat_shift_limit[0], sat_shift_limit[1])
            s = cv2.add(s, sat_shift)
            val_shift = np.random.uniform(val_shift_limit[0], val_shift_limit[1])
            v = cv2.add(v, val_shift)
            image = cv2.merge((h, s, v))
            image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
        return image

    def randomShiftScaleRotate(self, image, mask,
                               shift_limit=(-0.0, 0.0),
                               scale_limit=(-0.0, 0.0),
                               rotate_limit=(-0.0, 0.0),
                               aspect_limit=(-0.0, 0.0),
                               borderMode=cv2.BORDER_CONSTANT, u=0.5):
        if np.random.random() < u:
            height, width, channel = image.shape

            angle = np.random.uniform(rotate_limit[0], rotate_limit[1])
            scale = np.random.uniform(1 + scale_limit[0], 1 + scale_limit[1])
            aspect = np.random.uniform(1 + aspect_limit[0], 1 + aspect_limit[1])
            sx = scale * aspect / (aspect ** 0.5)
            sy = scale / (aspect ** 0.5)
            dx = round(np.random.uniform(shift_limit[0], shift_limit[1]) * width)
            dy = round(np.random.uniform(shift_limit[0], shift_limit[1]) * height)

            cc = np.math.cos(angle / 180 * np.math.pi) * sx
            ss = np.math.sin(angle / 180 * np.math.pi) * sy
            rotate_matrix = np.array([[cc, -ss], [ss, cc]])

            box0 = np.array([[0, 0], [width, 0], [width, height], [0, height], ])
            box1 = box0 - np.array([width / 2, height / 2])
            box1 = np.dot(box1, rotate_matrix.T) + np.array([width / 2 + dx, height / 2 + dy])

            box0 = box0.astype(np.float32)
            box1 = box1.astype(np.float32)
            mat = cv2.getPerspectiveTransform(box0, box1)
            image = cv2.warpPerspective(image, mat, (width, height), flags=cv2.INTER_LINEAR, borderMode=borderMode,
                                        borderValue=(
                                            0, 0,
                                            0,))
            mask = cv2.warpPerspective(mask, mat, (width, height), flags=cv2.INTER_LINEAR, borderMode=borderMode,
                                       borderValue=(
                                           0, 0,
                                           0,))

        return image, mask

    def randomHorizontalFlip(self, image, mask, u=0.5):
        if np.random.random() < u:
            image = cv2.flip(image, 1)
            mask = cv2.flip(mask, 1)
        return image, mask

    def randomVerticleFlip(self, image, mask, u=0.5):
        if np.random.random() < u:
            image = cv2.flip(image, 0)
            mask = cv2.flip(mask, 0)
        return image, mask

    def randomRotate90(self, image, mask, u=0.5):
        if np.random.random() < u:
            image = np.rot90(image)
            mask = np.rot90(mask)
        return image, mask

    def read(self, image_path, mask_path):
        image_path = image_path.decode()
        image = cv2.imread(image_path, cv2.IMREAD_COLOR)

        mask_path = mask_path.decode()
        mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
        return image, mask

    def resize(self, image, mask):
        image = cv2.resize(image, (self.image_size, self.image_size))
        mask = cv2.resize(mask, (self.image_size, self.image_size))
        return image, mask

    def normalize(self, image, mask):
        image = image / 255.
        image = image * 3.2 - 1.6
        mask = mask / 255.
        mask[mask >= 0.5] = 1
        mask[mask <= 0.5] = 0
        mask = np.expand_dims(mask, axis=-1)
        return image, mask

    def map_fn(self, image_path, mask_path):

        def process(image_path, mask_path):
            image, mask = self.read(image_path, mask_path)
            image, mask = self.resize(image, mask)
            if self.augment:
                image = self.randomHueSaturationValue(image,
                                 hue_shift_limit=(-30, 30),
                                 sat_shift_limit=(-5, 5),
                                 val_shift_limit=(-15, 15))

                image, mask = self.randomShiftScaleRotate(image, mask,
                                     shift_limit=(-0.1, 0.1),
                                     scale_limit=(-0.1, 0.1),
                                     aspect_limit=(-0.1, 0.1),
                                     rotate_limit=(-0, 0))
                image, mask = self.randomHorizontalFlip(image, mask)
                image, mask = self.randomVerticleFlip(image, mask)
                image, mask = self.randomRotate90(image, mask)

            image, mask = self.normalize(image, mask)
            return image, mask
        
        image, mask = tf.numpy_function(process, [image_path, mask_path], [tf.float64, tf.float64])

        image.set_shape([self.image_size, self.image_size, 3])
        mask.set_shape([self.image_size, self.image_size, 1])
        return image, mask

    def tf_data(self, batch_size):
        dataset = tf.data.Dataset.from_tensor_slices((self.image_paths, self.mask_paths))
        dataset = dataset.shuffle(len(self.image_paths), seed=101)
        dataset = dataset.map(self.map_fn, num_parallel_calls=AUTOTUNE)
        dataset = dataset.batch(batch_size)
        dataset = dataset.prefetch(buffer_size=AUTOTUNE)
        return dataset

Training and validation datasets:

train_data = data_loader(image_paths=train_images, mask_paths=train_masks, image_size=IMAGE_SIZE, augment=True).tf_data(batch_size=BATCH_SIZE)
val_data = data_loader(image_paths=val_images, mask_paths=val_masks, image_size=IMAGE_SIZE, augment=False).tf_data(batch_size=BATCH_SIZE)

I have set the random.seed, np.random.seed, and tf.random.set_seed before making the datasets but it's not working.

0

There are 0 answers