diff --git a/keras_cv/layers/preprocessing/random_resized_crop.py b/keras_cv/layers/preprocessing/random_resized_crop.py new file mode 100644 index 0000000000..71be2e66c0 --- /dev/null +++ b/keras_cv/layers/preprocessing/random_resized_crop.py @@ -0,0 +1,93 @@ +import tensorflow as tf +from keras.layers.preprocessing import preprocessing_utils as utils +from keras.utils import image_utils + +@tf.keras.utils.register_keras_serializable(package="keras_cv") +class RandomResizedCrop(tf.keras.internal.layers.BaseImageAugmentationLayer): + ''' + This layer is used to + Crop a random portion of image and resize it to a given size. + A crop of the original image is made: the crop has a random area (H * W) and a random aspect ratio. + This crop is finally resized to the given size. + This is popularly used to train the Inception networks. + ''' + + def __init__(self, height, width, seed=None, **kwargs): + super().__init__(seed=seed,**kwargs) + self.height = height + self.width = width + self.seed = seed + + #Method for taking the inputs + def augment_image(self, inputs, training=True): + inputs = utils.ensure_tensor(inputs, dtype=self.compute_dtype) + if training: + input_shape = tf.shape(inputs) + h_diff = input_shape[-3] - self.height + w_diff = input_shape[-2] - self.width + return tf.cond( + tf.reduce_all((h_diff >= 0, w_diff >= 0)), + lambda: self._random_crop(inputs), + lambda: self._resize(inputs)) + else: + return self._resize(inputs) + + #Method for the random crop + def _random_crop(self, inputs): + input_shape = tf.shape(inputs) + h_diff = input_shape[-3] - self.height + w_diff = input_shape[-2] - self.width + dtype = input_shape.dtype + rands = self._random_generator.random_uniform([2], 0, dtype.max, dtype) + h_start = rands[0] % (h_diff + 1) + w_start = rands[1] % (w_diff + 1) + return tf.image.crop_to_bounding_box(inputs, h_start, w_start, + self.height, self.width) + + # method for resizing the images + def _resize(self, inputs): + self.new_height,self.new_width=self.get_random_transformation() + outputs = image_utils.smart_resize(inputs, [self.new_height, self.new_width]) + # smart_resize will always output float32, so we need to re-cast. + return tf.cast(outputs, self.compute_dtype) + + # Method for computing the output shape + def compute_output_shape(self, input_shape): + input_shape = tf.TensorShape(input_shape).as_list() + input_shape[-3] = self.height + input_shape[-2] = self.width + return tf.TensorShape(input_shape) + + #Configuration Method + def get_config(self): + config = { + 'height': self.height, + 'width': self.width, + 'seed': self.seed, + } + base_config = super(RandomResizedCrop, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + def get_random_transformation(self, image=None, label=None, bounding_box=None): + x = self._get_shear_amount(self.height) + + y = self._get_shear_amount(self.width) + + return (x, y) + + def _get_shear_amount(self, constraint): + if constraint is None: + return None + + negate = tf.random_uniform((), 0, self.constraint, dtype=tf.float32) > 0.5 + negate = tf.cond(negate, lambda: -1.0, lambda: 1.0) + + return negate * self._random_generator.random_uniform( + (), constraint[0], constraint[1] + ) + + # Method for Random Resizing of the inputs + def _random_resized_crop(self,inputs): + return self._resize(self,self._random_crop(self,inputs)) + + diff --git a/keras_cv/layers/preprocessing/random_resized_crop_test.py b/keras_cv/layers/preprocessing/random_resized_crop_test.py new file mode 100644 index 0000000000..0cb010b703 --- /dev/null +++ b/keras_cv/layers/preprocessing/random_resized_crop_test.py @@ -0,0 +1,24 @@ +import tensorflow as tf + +from keras_cv.layers import preprocessing + + +class RandomResizedCropTest(tf.test.TestCase): + def test_aggressive_shear_fills_at_least_some_pixels(self): + img_shape = (50, 50, 3) + xs = tf.stack( + [2 * tf.ones(img_shape), tf.ones(img_shape)], + axis=0, + ) + xs = tf.cast(xs, tf.float32) + + fill_value = 0.0 + layer = preprocessing.RandomResizedCrop( + height=3, width=5, seed=0) + xs = layer(xs) + + # Some pixels should be replaced with fill value + self.assertTrue(tf.math.reduce_any(xs[0] == fill_value)) + self.assertTrue(tf.math.reduce_any(xs[0] == 2.0)) + self.assertTrue(tf.math.reduce_any(xs[1] == fill_value)) + self.assertTrue(tf.math.reduce_any(xs[1] == 1.0)) \ No newline at end of file