diff --git a/tensorflow_addons/image/README.md b/tensorflow_addons/image/README.md index 973c53e7cd..1db80c1a47 100644 --- a/tensorflow_addons/image/README.md +++ b/tensorflow_addons/image/README.md @@ -17,6 +17,7 @@ | distance_transform_ops | euclidean_distance_transform | | | distort_image_ops | adjust_hsv_in_yiq | | | distort_image_ops | random_hsv_in_yiq | | +| filters | mean_filter2d | | | filters | median_filter2d | | | transform_ops | angles_to_projective_transforms | | | transform_ops | matrices_to_flat_transforms | | diff --git a/tensorflow_addons/image/__init__.py b/tensorflow_addons/image/__init__.py index d72aae2fea..15028405f8 100644 --- a/tensorflow_addons/image/__init__.py +++ b/tensorflow_addons/image/__init__.py @@ -22,6 +22,7 @@ from tensorflow_addons.image.distance_transform import euclidean_dist_transform from tensorflow_addons.image.distort_image_ops import adjust_hsv_in_yiq from tensorflow_addons.image.distort_image_ops import random_hsv_in_yiq +from tensorflow_addons.image.filters import mean_filter2d from tensorflow_addons.image.filters import median_filter2d from tensorflow_addons.image.transform_ops import rotate from tensorflow_addons.image.transform_ops import transform diff --git a/tensorflow_addons/image/filters.py b/tensorflow_addons/image/filters.py index 7108119a1f..61b5334496 100644 --- a/tensorflow_addons/image/filters.py +++ b/tensorflow_addons/image/filters.py @@ -21,8 +21,22 @@ @tf.function -def median_filter2d(image, filter_shape=(3, 3), name=None): - """This method performs Median Filtering on image. Filter shape can be user +def _normalize(li, ma): + one = tf.convert_to_tensor(1.0) + two = tf.convert_to_tensor(255.0) + + def func1(): + return li + + def func2(): + return tf.math.truediv(li, two) + + return tf.cond(tf.math.greater(ma, one), func2, func1) + + +@tf.function +def mean_filter2d(image, filter_shape=(3, 3), name=None): + """This method performs Mean Filtering on image. Filter shape can be user given. This method takes both kind of images where pixel values lie between 0 to @@ -36,24 +50,90 @@ def median_filter2d(image, filter_shape=(3, 3), name=None): C is the second value in the filter is the number of columns in the filter. This creates a filter of shape (R,C) or RxC filter. Default value = (3,3) - name: The name of the op. Returns: - A 3D median filtered image tensor of shape [rows,columns,channels] and + A 3D mean filtered image tensor of shape [rows,columns,channels] and type 'int32'. Pixel value of returned tensor ranges between 0 to 255 """ - def _normalize(li): - one = tf.convert_to_tensor(1.0) - two = tf.convert_to_tensor(255.0) + with tf.name_scope(name or "mean_filter2d"): + if not isinstance(filter_shape, tuple): + raise TypeError('Filter shape must be a tuple') + if len(filter_shape) != 2: + raise ValueError('Filter shape must be a tuple of 2 integers. ' + 'Got %s values in tuple' % len(filter_shape)) + filter_shapex = filter_shape[0] + filter_shapey = filter_shape[1] + if not isinstance(filter_shapex, int) or not isinstance( + filter_shapey, int): + raise TypeError('Size of the filter must be Integers') + (row, col, ch) = (image.shape[0], image.shape[1], image.shape[2]) + if row != None and col != None and ch != None: + (row, col, ch) = (int(row), int(col), int(ch)) + else: + raise TypeError( + 'All the Dimensions of the input image tensor must be \ + Integers.') + if row < filter_shapex or col < filter_shapey: + raise ValueError( + 'Number of Pixels in each dimension of the image should be \ + more than the filter size. Got filter_shape (%sx' % + filter_shape[0] + '%s).' % filter_shape[1] + + ' Image Shape (%s)' % image.shape) + if filter_shapex % 2 == 0 or filter_shapey % 2 == 0: + raise ValueError('Filter size should be odd. Got filter_shape (%sx' + % filter_shape[0] + '%s)' % filter_shape[1]) + image = tf.cast(image, tf.float32) + tf_i = tf.reshape(image, [row * col * ch]) + ma = tf.math.reduce_max(tf_i) + image = _normalize(image, ma) - def func1(): - return li + # k and l is the Zero-padding size - def func2(): - return tf.math.truediv(li, two) + listi = [] + for a in range(ch): + img = image[:, :, a:a + 1] + img = tf.reshape(img, [1, row, col, 1]) + slic = tf.image.extract_patches( + img, [1, filter_shapex, filter_shapey, 1], [1, 1, 1, 1], + [1, 1, 1, 1], + padding='SAME') + li = tf.reduce_mean(slic, axis=-1) + li = tf.reshape(li, [row, col, 1]) + listi.append(li) + y = tf.concat(listi[0], 2) + + for i in range(len(listi) - 1): + y = tf.concat([y, listi[i + 1]], 2) + + y *= 255 + y = tf.cast(y, tf.int32) + + return y + + +@tf.function +def median_filter2d(image, filter_shape=(3, 3), name=None): + """This method performs Median Filtering on image. Filter shape can be user + given. + + This method takes both kind of images where pixel values lie between 0 to + 255 and where it lies between 0.0 and 1.0 + Args: + image: A 3D `Tensor` of type `float32` or 'int32' or 'float64' or + 'int64 and of shape`[rows, columns, channels]` + + filter_shape: Optional Argument. A tuple of 2 integers (R,C). + R is the first value is the number of rows in the filter and + C is the second value in the filter is the number of columns + in the filter. This creates a filter of shape (R,C) or RxC + filter. Default value = (3,3) + name: The name of the op. - return tf.cond(tf.math.greater(ma, one), func2, func1) + Returns: + A 3D median filtered image tensor of shape [rows,columns,channels] and + type 'int32'. Pixel value of returned tensor ranges between 0 to 255 + """ with tf.name_scope(name or "median_filter2d"): if not isinstance(filter_shape, tuple): @@ -74,16 +154,17 @@ def func2(): 'tensor must be Integers.') if row < filter_shapex or col < filter_shapey: raise ValueError( - 'No of Pixels in each dimension of the image should be more \ - than the filter size. Got filter_shape (%sx' % filter_shape[0] - + '%s).' % filter_shape[1] + ' Image Shape (%s)' % image.shape) + 'Number of Pixels in each dimension of the image should be \ + more than the filter size. Got filter_shape (%sx' % + filter_shape[0] + '%s).' % filter_shape[1] + + ' Image Shape (%s)' % image.shape) if filter_shapex % 2 == 0 or filter_shapey % 2 == 0: raise ValueError('Filter size should be odd. Got filter_shape ' '(%sx%s)' % (filter_shape[0], filter_shape[1])) image = tf.cast(image, tf.float32) tf_i = tf.reshape(image, [row * col * ch]) ma = tf.math.reduce_max(tf_i) - image = _normalize(image) + image = _normalize(image, ma) # k and l is the Zero-padding size diff --git a/tensorflow_addons/image/filters_test.py b/tensorflow_addons/image/filters_test.py index 2eadbafc87..c4a3bb4bab 100644 --- a/tensorflow_addons/image/filters_test.py +++ b/tensorflow_addons/image/filters_test.py @@ -18,10 +18,80 @@ from __future__ import print_function import tensorflow as tf +from tensorflow_addons.image import mean_filter2d from tensorflow_addons.image import median_filter2d from tensorflow_addons.utils import test_utils +class MeanFilter2dTest(tf.test.TestCase): + def _validate_mean_filter2d(self, + inputs, + expected_values, + filter_shape=(3, 3)): + output = mean_filter2d(inputs, filter_shape) + self.assertAllClose(output, expected_values) + + @test_utils.run_in_graph_and_eager_modes + def test_filter_tuple(self): + tf_img = tf.zeros([3, 4, 3], tf.int32) + + for filter_shape in [3, 3.5, 'dt', None]: + with self.assertRaisesRegexp(TypeError, + 'Filter shape must be a tuple'): + mean_filter2d(tf_img, filter_shape) + + filter_shape = (3, 3, 3) + msg = ('Filter shape must be a tuple of 2 integers. ' + 'Got %s values in tuple' % len(filter_shape)) + with self.assertRaisesRegexp(ValueError, msg): + mean_filter2d(tf_img, filter_shape) + + msg = 'Size of the filter must be Integers' + for filter_shape in [(3.5, 3), (None, 3)]: + with self.assertRaisesRegexp(TypeError, msg): + mean_filter2d(tf_img, filter_shape) + + @test_utils.run_in_graph_and_eager_modes + def test_filter_value(self): + tf_img = tf.zeros([3, 4, 3], tf.int32) + + with self.assertRaises(ValueError): + mean_filter2d(tf_img, (4, 3)) + + @test_utils.run_deprecated_v1 + def test_dimension(self): + for image_shape in [(3, 4, None), (3, None, 4), (None, 3, 4)]: + with self.assertRaises(TypeError): + tf_img = tf.compat.v1.placeholder(tf.int32, shape=image_shape) + mean_filter2d(tf_img) + + @test_utils.run_in_graph_and_eager_modes + def test_image_vs_filter(self): + tf_img = tf.zeros([3, 4, 3], tf.int32) + filter_shape = (3, 5) + with self.assertRaises(ValueError): + mean_filter2d(tf_img, filter_shape) + + @test_utils.run_in_graph_and_eager_modes + def test_three_channels(self): + tf_img = [[[0.32801723, 0.08863795, 0.79119259], + [0.35526001, 0.79388736, 0.55435993], + [0.11607035, 0.55673079, 0.99473371]], + [[0.53240645, 0.74684819, 0.33700031], + [0.01760473, 0.28181609, 0.9751476], + [0.01605137, 0.8292904, 0.56405609]], + [[0.57215374, 0.10155051, 0.64836128], + [0.36533048, 0.91401874, 0.02524159], + [0.56379134, 0.9028874, 0.19505117]]] + + tf_img = tf.convert_to_tensor(value=tf_img) + expt = [[[34, 54, 75], [38, 93, 119], [14, 69, 87]], + [[61, 82, 94], [81, 147, 144], [40, 121, 93]], + [[42, 57, 56], [58, 106, 77], [27, 82, 49]]] + expt = tf.convert_to_tensor(value=expt) + self._validate_mean_filter2d(tf_img, expt) + + class MedianFilter2dTest(tf.test.TestCase): def _validate_median_filter2d(self, inputs,