|
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
-
- import tensorflow as tf
- import numpy as np
- from tensorflow.python.ops import math_ops
- from tensorflow.python.ops import array_ops, random_ops
- from tensorflow.python.framework import ops
- from tensorflow.python.ops.image_ops_impl import _AssertAtLeast3DImage
- from tensorflow.python.framework import dtypes
- from tensorflow.python.ops.image_ops_impl import convert_image_dtype
- import numbers
- import PIL
- from PIL import Image
- import math
- import scipy
- from scipy import ndimage
- __all__ = [
- 'central_crop',
- 'to_tensor',
- 'crop',
- 'pad',
- 'resize',
- 'transpose',
- 'hwc_to_chw',
- 'chw_to_hwc',
- 'rgb_to_hsv',
- 'hsv_to_rgb',
- 'rgb_to_gray',
- 'adjust_brightness',
- 'adjust_contrast',
- 'adjust_hue',
- 'adjust_saturation',
- 'normalize',
- 'hflip',
- 'vflip',
- 'padtoboundingbox',
- 'standardize',
- 'random_brightness',
- 'random_contrast',
- 'random_saturation',
- 'random_hue',
- 'random_crop',
- 'random_resized_crop',
- 'random_vflip',
- 'random_hflip',
- 'random_rotation',
- 'random_shear',
- 'random_shift',
- 'random_zoom',
- 'random_affine',
- ]
-
-
- def _is_pil_image(image):
- return isinstance(image, Image.Image)
-
-
- def _is_numpy_image(image):
- return isinstance(image, np.ndarray) and (image.ndim in {2, 3})
-
-
- def _get_image_size(image):
- image_shape = image.get_shape()
- if image_shape.ndims == 3:
- height, width, channels = image_shape
- return height, width
- elif image_shape.ndims == 4:
- batch, height, width, channels = image_shape
- return height, width
-
-
- def random_factor(factor, name, center=1, bound=(0, float('inf')), non_negative=True):
- if isinstance(factor, numbers.Number):
- if factor < 0:
- raise ValueError('The input value of {} cannot be negative.'.format(name))
- factor = [center - factor, center + factor]
- if non_negative:
- factor[0] = max(0, factor[0])
- elif isinstance(factor, (tuple, list)) and len(factor) == 2:
- if not bound[0] <= factor[0] <= factor[1] <= bound[1]:
- raise ValueError(
- "Please check your value range of {} is valid and "
- "within the bound {}.".format(name, bound)
- )
- else:
- raise TypeError("Input of {} should be either a single value, or a list/tuple of " "length 2.".format(name))
- factor = np.random.uniform(factor[0], factor[1])
- return factor
-
-
- def central_crop(image, size=None, central_fraction=None):
- '''
-
- Parameters
- ----------
- image :
- input Either a 3-D float Tensor of shape [height, width, depth],
- or a 4-D Tensor of shape [batch_size, height, width, depth].
- central_fraction :
- float (0, 1], fraction of size to crop
- size:
- size (Union[int, sequence]) – The output size of the cropped image. If size is an integer, a square crop of size (size, size) is returned.
- If size is a sequence of length 2, it should be (height, width).
- Returns :
- 3-D / 4-D float Tensor, as per the input.
- -------
-
- '''
- if size is None and central_fraction is None:
- raise ValueError('central_fraction and size can not be both None')
-
- if size is not None:
- if not isinstance(size, (int, list, tuple)) or (isinstance(size, (list, tuple)) and len(size) != 2):
- raise ValueError(
- "Size should be a single integer or a list/tuple (h, w) of length 2.But"
- "got {}.".format(type(size))
- )
- if isinstance(size, int):
- target_height = size
- target_width = size
- else:
- target_height = size[0]
- target_width = size[1]
- image = ops.convert_to_tensor(image, name='image')
- rank = image.get_shape().ndims
- if rank != 3 and rank != 4:
- raise ValueError(
- '`image` should either be a Tensor with rank = 3 or '
- 'rank = 4. Had rank = {}.'.format(rank)
- )
-
- def _get_dim(tensor, idx):
- static_shape = tensor.get_shape().dims[idx].value
- if static_shape is not None:
- return static_shape, False
- return array_ops.shape(tensor)[idx], True
-
- if rank == 3:
- img_h, dynamic_h = _get_dim(image, 0)
- img_w, dynamic_w = _get_dim(image, 1)
- img_d = image.get_shape()[2]
- else:
- img_bs = image.get_shape()[0]
- img_h, dynamic_h = _get_dim(image, 1)
- img_w, dynamic_w = _get_dim(image, 2)
- img_d = image.get_shape()[3]
-
- bbox_h_size = target_height
- bbox_w_size = target_width
-
- if dynamic_h:
- img_hd = math_ops.cast(img_h, dtypes.float64)
- target_height = math_ops.cast(target_height, dtypes.float64)
- bbox_h_start = math_ops.cast((img_hd - target_height) / 2, dtypes.int32)
- else:
- img_hd = float(img_h)
- target_height = float(target_height)
- bbox_h_start = int((img_hd - target_height) / 2)
-
- if dynamic_w:
- img_wd = math_ops.cast(img_w, dtypes.float64)
- target_width = math_ops.cast(target_width, dtypes.float64)
- bbox_w_start = math_ops.cast((img_wd - target_width) / 2, dtypes.int32)
- else:
- img_wd = float(img_w)
- target_width = float(target_width)
- bbox_w_start = int((img_wd - target_width) / 2)
-
- if rank == 3:
- bbox_begin = array_ops.stack([bbox_h_start, bbox_w_start, 0])
- bbox_size = array_ops.stack([bbox_h_size, bbox_w_size, -1])
- else:
- bbox_begin = array_ops.stack([0, bbox_h_start, bbox_w_start, 0])
- bbox_size = array_ops.stack([-1, bbox_h_size, bbox_w_size, -1])
-
- image = array_ops.slice(image, bbox_begin, bbox_size)
-
- if rank == 3:
- image.set_shape([None if dynamic_h else bbox_h_size, None if dynamic_w else bbox_w_size, img_d])
- else:
- image.set_shape([img_bs, None if dynamic_h else bbox_h_size, None if dynamic_w else bbox_w_size, img_d])
- return image
-
- elif central_fraction is not None:
- return tf.image.central_crop(image, central_fraction)
-
-
- def to_tensor(img, data_format):
- '''Converts a ``image`` to tf.Tensor.
-
- Parameters
- ----------
- img:
- Image to be converted to tensor.
- data_format:
- Data format of output tensor, should be 'HWC' or
- 'CHW'. Default: 'HWC'.
-
- Returns:
- Tensor: Converted image.
- -------
-
- '''
- if not (_is_pil_image(img) or _is_numpy_image(img)):
- raise TypeError('img should be PIL Image or ndarray. But got {}'.format(type(img)))
-
- if _is_pil_image(img):
- # PIL Image
- if img.mode == 'I':
- image = tf.convert_to_tensor(np.array(img, np.int32, copy=False))
- elif img.mode == 'I;16':
- # cast and reshape not support int16
- image = tf.convert_to_tensor(np.array(img, np.int32, copy=False))
- elif img.mode == 'F':
- image = tf.convert_to_tensor(np.array(img, np.float32, copy=False))
- elif img.mode == '1':
- image = 255 * tf.convert_to_tensor(np.array(img, np.uint8, copy=False))
- else:
- image = tf.convert_to_tensor(np.array(img, copy=False))
-
- if img.mode == 'YCbCr':
- nchannel = 3
- elif img.mode == 'I;16':
- nchannel = 1
- else:
- nchannel = len(img.mode)
-
- dtype = image.dtype
- if dtype == 'tf.uint8':
- image = tf.cast(image, tf.float32) / 255.
-
- image = tf.reshape(image, shape=[img.size[1], img.size[0], nchannel])
- if data_format == 'CHW':
- image = tf.transpose(image, perm=[2, 0, 1])
- return image
- else:
- if img.ndim == 2:
- img = img[:, :, None]
-
- if data_format == 'CHW':
- img = tf.convert_to_tensor(img.transpose((2, 0, 1)))
- else:
- img = tf.convert_to_tensor(img)
-
- dtype = img.dtype
- if dtype == 'tf.uint8':
- img = tf.cast(img, tf.float32) / 255.
- return img
-
-
- def crop(image, offset_height, offset_width, target_height, target_width):
-
- return tf.image.crop_to_bounding_box(image, offset_height, offset_width, target_height, target_width)
-
-
- def pad(image, padding, padding_value, mode):
- '''
-
- Parameters
- ----------
- image:
- A 3-D or 4-D Tensor.
- padding:
- An integer or a list/tuple. If a single number is provided, pad all borders with this value.
- If a tuple or list of 2 values is provided, pad the left and right with the first value and the top and bottom with the second value.
- If 4 values are provided as a list or tuple, pad the (left , top, right, bottom) respectively.
- padding_value:
- In "CONSTANT" mode, the scalar pad value to use. Must be same type as tensor.
- mode:
- One of "CONSTANT", "REFLECT", or "SYMMETRIC" (case-insensitive)
- Returns:
- A padded Tensor. Has the same type as tensor.
- -------
-
- '''
- image = ops.convert_to_tensor(image, name='image')
- image_shape = image.get_shape()
- if len(image_shape) == 3:
- batch_size = 0
- elif len(image_shape) == 4:
- batch_size = image_shape[0]
- else:
- raise TypeError('Image must be a 3-D tensor or 4-D tensor.')
-
- if isinstance(padding, int):
- padding = ((padding, padding), (padding, padding))
- elif isinstance(padding, list) or isinstance(padding, tuple):
- if len(padding) == 2:
- padding = ((padding[1], padding[1]), (padding[0], padding[0]))
- elif len(padding) == 4:
- padding = ((padding[1], padding[3]), (padding[0], padding[2]))
- else:
- raise ValueError('The length of padding should be 2 or 4, but got {}.'.format(len(padding)))
- else:
- raise TypeError('Padding should be an integer or a list/tuple, but got {}.'.format(type(padding)))
-
- if batch_size == 0:
- padding = (padding[0], padding[1], (0, 0))
- else:
- padding = ((0, 0), padding[0], padding[1], (0, 0))
-
- return tf.pad(image, padding, mode=mode, constant_values=padding_value)
-
-
- def resize(image, size, method):
- '''
-
- Parameters
- ----------
- images:
- Input images to resize
- size:
- The output size of the resized image.
- If size is an integer, smaller edge of the image will be resized to this value with
- the same image aspect ratio.
- If size is a sequence of (height, width), this will be the desired output size.
- method:
- An image.ResizeMethod, or string equivalent shoulid be in
- (bilinear, lanczos3, lanczos5, bicubic, gaussian, nearest, area, mitchellcubic).
- Defaults to bilinear.
- preserve_aspect_ratio:
- Whether to preserve the aspect ratio.
- Returns:
- resized images
- -------
-
- '''
- if not (isinstance(size, int) or (isinstance(size, (list, tuple)) and len(size) == 2)):
- raise TypeError('Size should be a single number or a list/tuple (h, w) of length 2.' 'Got {}.'.format(size))
- image = ops.convert_to_tensor(image)
- orig_dtype = image.dtype
- if orig_dtype not in [dtypes.float16, dtypes.float32]:
- image = convert_image_dtype(image, dtypes.float32)
-
- if image.get_shape().ndims == 3:
- h, w, _ = image.get_shape().as_list()
- elif image.get_shape().ndims == 4:
- _, h, w, _ = image.get_shape().as_list()
-
- if isinstance(size, int):
- if (w <= h and w == size) or (h <= w and h == size):
- size = (h, w)
- if w < h:
- target_w = size
- target_h = int(size * h / w)
- size = (target_h, target_w)
- else:
- target_h = size
- target_w = int(size * w / h)
- size = (target_h, target_w)
- image = tf.image.resize(image, size, method, preserve_aspect_ratio=False)
- return convert_image_dtype(image, orig_dtype, saturate=True)
-
-
- def transpose(image, order):
- image = ops.convert_to_tensor(image)
- shape = image.get_shape()
- if shape.ndims == 3 or shape.ndims is None:
- if len(order) != 3:
- raise ValueError('if image is 3-D tensor, order should be a list/tuple with length of 3')
- return array_ops.transpose(image, order)
- elif shape.ndims == 4:
- if len(order) != 4:
- raise ValueError('if image is 4-D tensor, order should be a list/tuple with length of 4')
- return array_ops.transpose(image, order)
- else:
- raise ValueError('\'image\' must have either 3 or 4 dimensions.')
-
-
- def hwc_to_chw(image):
-
- if (len(image.shape) == 3):
- return transpose(image, (2, 0, 1))
- elif (len(image.shape) == 4):
- return transpose(image, (0, 3, 1, 2))
- else:
- raise ValueError('\'image\' must have either 3 or 4 dimensions.')
-
-
- def chw_to_hwc(image):
-
- if (len(image.shape) == 3):
- return transpose(image, (1, 2, 0))
- elif (len(image.shape) == 4):
- return transpose(image, (0, 2, 3, 1))
- else:
- raise ValueError('\'image\' must have either 3 or 4 dimensions.')
-
-
- def rgb_to_hsv(image):
-
- return tf.image.rgb_to_hsv(image)
-
-
- def hsv_to_rgb(image):
-
- return tf.image.hsv_to_rgb(image)
-
-
- def rgb_to_gray(image, num_output_channels):
-
- if num_output_channels not in (1, 3):
- raise ValueError('num_output_channels should be either 1 or 3')
-
- image = ops.convert_to_tensor(image, name='image')
- orig_dtype = image.dtype
- flt_image = convert_image_dtype(image, dtypes.float32)
- rgb_weights = [0.2989, 0.5870, 0.1140]
- gray_float = math_ops.tensordot(flt_image, rgb_weights, [-1, -1])
- gray_float = array_ops.expand_dims(gray_float, -1)
- if num_output_channels == 3:
- gray_float = array_ops.stack([gray_float, gray_float, gray_float], axis=2)
- return convert_image_dtype(gray_float, orig_dtype)
-
-
- def adjust_brightness(image, brightness_factor):
- '''
- Parameters
- ----------
- images:
- Input images to adjust brightness
- brightness_factor(float): How much to adjust the brightness. Can be
- any non negative number. 0 gives a black image, 1 gives the
- original image while 2 increases the brightness by a factor of 2.
- Returns:
- adjusted images
- -------
- '''
- if brightness_factor < 0:
- raise ValueError('brightness_factor ({}) is not non-negative.'.format(brightness_factor))
-
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
-
- orig_dtype = image.dtype
- if orig_dtype not in [dtypes.float16, dtypes.float32]:
- image = convert_image_dtype(image, dtypes.float32)
-
- brightness_factor = math_ops.cast(brightness_factor, image.dtype)
- image_zeros = tf.zeros_like(image)
- adjusted = brightness_factor * image + (1.0 - brightness_factor) * image_zeros
- adjusted = tf.clip_by_value(adjusted, clip_value_min=0, clip_value_max=1.0)
- return convert_image_dtype(adjusted, orig_dtype, saturate=True)
-
-
- def adjust_contrast(image, contrast_factor):
- '''
- Parameters
- ----------
- images:
- Input images to adjust contrast
- contrast_factor(float): How much to adjust the contrast. Can be
- any non negative number. 0 gives a gray image, 1 gives the
- original image while 2 increases the contrast by a factor of 2.
- Returns:
- adjusted images
- -------
- '''
- if contrast_factor < 0:
- raise ValueError('contrast_factor ({}) is not non-negative.'.format(contrast_factor))
-
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
-
- orig_dtype = image.dtype
- if orig_dtype not in [dtypes.float16, dtypes.float32]:
- image = convert_image_dtype(image, dtypes.float32)
-
- contrast_factor = math_ops.cast(contrast_factor, image.dtype)
- mean = tf.math.reduce_mean(tf.image.rgb_to_grayscale(image), keepdims=True)
- adjusted = contrast_factor * image + (1 - contrast_factor) * mean
- adjusted = tf.clip_by_value(adjusted, clip_value_min=0, clip_value_max=1.0)
- return convert_image_dtype(adjusted, orig_dtype, saturate=True)
-
-
- def adjust_hue(image, hue_factor):
- '''
- Parameters
- ----------
- images(Tensor):
- Input images to adjust hue
- hue_factor(float): How much to shift the hue channel. Should be in
- [-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in
- HSV space in positive and negative direction respectively.
- 0 means no shift. Therefore, both -0.5 and 0.5 will give an image
- with complementary colors while 0 gives the original image.
- Returns(Tensor):
- Adjusted images
- -------
- '''
- if not (-0.5 <= hue_factor <= 0.5):
- raise ValueError('hue_factor ({}) is not in [-0.5, 0.5].'.format(hue_factor))
-
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
-
- orig_dtype = image.dtype
- if orig_dtype not in [dtypes.float16, dtypes.float32]:
- image = convert_image_dtype(image, dtypes.float32)
-
- hue_factor = math_ops.cast(hue_factor, image.dtype)
- image = tf.image.rgb_to_hsv(image)
- h, s, v = tf.split(image, num_or_size_splits=[1, 1, 1], axis=2)
- h = (h + hue_factor) % 1.0
- image = tf.concat((h, s, v), axis=2)
- adjusted = tf.image.hsv_to_rgb(image)
-
- return convert_image_dtype(adjusted, orig_dtype, saturate=True)
-
-
- def adjust_saturation(image, saturation_factor):
- '''
- Parameters
- ----------
- images(Tensor):
- Input images to adjust saturation
- contrast_factor(float): How much to adjust the saturation. 0 will
- give a black and white image, 1 will give the original image while
- 2 will enhance the saturation by a factor of 2.
- Returns(Tensor):
- Adjusted images
- -------
- '''
- if saturation_factor < 0:
- raise ValueError('saturation_factor ({}) is not non-negative.'.format(saturation_factor))
-
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
-
- orig_dtype = image.dtype
- if orig_dtype not in [dtypes.float16, dtypes.float32]:
- image = convert_image_dtype(image, dtypes.float32)
-
- saturation_factor = math_ops.cast(saturation_factor, image.dtype)
- gray_image = tf.image.rgb_to_grayscale(image)
- adjusted = saturation_factor * image + (1 - saturation_factor) * gray_image
- adjusted = tf.clip_by_value(adjusted, clip_value_min=0, clip_value_max=1.0)
- return convert_image_dtype(adjusted, orig_dtype, saturate=True)
-
-
- def hflip(image):
- '''
-
- Parameters
- ----------
- image(Tensor):
- Input images to flip an image horizontally (left to right)
-
- Returns(Tensor):
- Flipped images
- -------
-
- '''
- return tf.image.flip_left_right(image)
-
-
- def vflip(image):
- '''
-
- Parameters
- ----------
- image(Tensor):
- Input images to flip an image vertically (up to down)
-
- Returns(Tensor):
- Flipped images
- -------
-
- '''
- return tf.image.flip_up_down(image)
-
-
- def padtoboundingbox(image, offset_height, offset_width, target_height, target_width, padding_value):
- '''
-
- Parameters
- ----------
- image:
- 4-D Tensor of shape `[batch, height, width, channels]` or 3-D Tensor
- of shape `[height, width, channels]`.
- offset_height:
- Number of rows of padding_values to add on top.
- offset_width:
- Number of columns of padding_values to add on the left.
- target_height:
- Height of output image.
- target_width:
- Width of output image.
- padding_value:
- value to pad
-
- Returns:
- If `image` was 4-D, a 4-D float Tensor of shape
- `[batch, target_height, target_width, channels]`
- If `image` was 3-D, a 3-D float Tensor of shape
- `[target_height, target_width, channels]`
- -------
-
- '''
- image = ops.convert_to_tensor(image, name='image')
-
- if offset_height < 0:
- raise ValueError('offset_height must be >= 0')
- if offset_width < 0:
- raise ValueError('offset_width must be >= 0')
-
- image_shape = image.get_shape()
- if image_shape.ndims == 3:
- height, width, channels = image.get_shape()
- elif image_shape.ndims == 4:
- batch, height, width, channels = image.get_shape()
- else:
- raise ValueError('\'image\' (shape %s) must have either 3 or 4 dimensions.' % image_shape)
-
- after_padding_width = target_width - offset_width - width
- after_padding_height = target_height - offset_height - height
- if after_padding_height < 0:
- raise ValueError('image height must be <= target - offset')
- if after_padding_width < 0:
- raise ValueError('image width must be <= target - offset')
-
- return pad(
- image, padding=(offset_width, offset_height, after_padding_width, after_padding_height),
- padding_value=padding_value, mode='constant'
- )
-
-
- def normalize(image, mean, std, data_format):
- '''
- Parameters
- ----------
- image:
- An n-D Tensor with at least 3 dimensions, the last 3 of which are the dimensions of each image.
- mean:
- List or tuple of mean values for each channel, with respect to channel order.
- std:
- List or tuple of standard deviations for each channel.
- channel_mode:
- Decide to implement standardization on whole image or each channel of image.
- Returns:
- A Tensor with the same shape and dtype as image.
- -------
- '''
- image = ops.convert_to_tensor(image, name='image')
- image = math_ops.cast(image, dtype=tf.float32)
- image = _AssertAtLeast3DImage(image)
-
- if data_format == 'CHW':
- num_channels = image.shape[0]
- elif data_format == 'HWC':
- num_channels = image.shape[2]
-
- if isinstance(mean, numbers.Number):
- mean = (mean, ) * num_channels
- elif isinstance(mean, (list, tuple)):
- if len(mean) != num_channels:
- raise ValueError("Length of mean must be 1 or equal to the number of channels({0}).".format(num_channels))
- if isinstance(std, numbers.Number):
- std = (std, ) * num_channels
- elif isinstance(std, (list, tuple)):
- if len(std) != num_channels:
- raise ValueError("Length of std must be 1 or equal to the number of channels({0}).".format(num_channels))
-
- if data_format == 'CHW':
- std = np.float32(np.array(std).reshape((-1, 1, 1)))
- mean = np.float32(np.array(mean).reshape((-1, 1, 1)))
- elif data_format == 'HWC':
- mean = np.float32(np.array(mean).reshape((1, 1, -1)))
- std = np.float32(np.array(std).reshape((1, 1, -1)))
-
- mean = ops.convert_to_tensor(mean)
- mean = math_ops.cast(mean, dtype=tf.float32)
- std = ops.convert_to_tensor(std)
- std = math_ops.cast(std, dtype=tf.float32)
- image -= mean
- image = math_ops.divide(image, std)
- return image
-
-
- def standardize(image):
- '''
- Reference to tf.image.per_image_standardization().
- Linearly scales each image in image to have mean 0 and variance 1.
-
- Parameters
- ----------
- image:
- An n-D Tensor with at least 3 dimensions, the last 3 of which are the dimensions of each image.
-
- Returns:
- A Tensor with the same shape as image and its dtype is float32.
- -------
-
- '''
- image = ops.convert_to_tensor(image, name='image')
- image = math_ops.cast(image, dtype=tf.float32)
- return tf.image.per_image_standardization(image)
-
-
- def random_brightness(image, brightness_factor):
- '''
- Perform a random brightness on the input image.
- Parameters
- ----------
- image:
- Input images to adjust random brightness
- brightness_factor:
- Brightness adjustment factor (default=(1, 1)). Cannot be negative.
- If it is a float, the factor is uniformly chosen from the range [max(0, 1-brightness), 1+brightness].
- If it is a sequence, it should be [min, max] for the range.
-
- Returns:
- Adjusted image.
- -------
-
- '''
- brightness_factor = random_factor(brightness_factor, name='brightness')
-
- return adjust_brightness(image, brightness_factor)
-
-
- def random_contrast(image, contrast_factor):
- '''
- Perform a random contrast on the input image.
- Parameters
- ----------
- image:
- Input images to adjust random contrast
- contrast_factor:
- Contrast adjustment factor (default=(1, 1)). Cannot be negative.
- If it is a float, the factor is uniformly chosen from the range [max(0, 1-contrast), 1+contrast].
- If it is a sequence, it should be [min, max] for the range.
-
- Returns:
- Adjusted image.
- -------
-
- '''
- contrast_factor = random_factor(contrast_factor, name='contrast')
-
- return adjust_contrast(image, contrast_factor)
-
-
- def random_saturation(image, saturation_factor):
- '''
- Perform a random saturation on the input image.
- Parameters
- ----------
- image:
- Input images to adjust random saturation
- saturation_factor:
- Saturation adjustment factor (default=(1, 1)). Cannot be negative.
- If it is a float, the factor is uniformly chosen from the range [max(0, 1-saturation), 1+saturation].
- If it is a sequence, it should be [min, max] for the range.
-
- Returns:
- Adjusted image.
- -------
-
- '''
-
- saturation_factor = random_factor(saturation_factor, name='saturation')
-
- return adjust_saturation(image, saturation_factor)
-
-
- def random_hue(image, hue_factor):
- '''
- Perform a random contrast on the input image.
- Parameters
- ----------
- image:
- Input images to adjust random contrast
- brightness_factor:
- Contrast adjustment factor (default=(1, 1)). Cannot be negative.
- If it is a float, the factor is uniformly chosen from the range [max(0, 1-contrast), 1+contrast].
- If it is a sequence, it should be [min, max] for the range.
-
- Returns:
- Adjusted image.
- -------
-
- '''
- hue_factor = random_factor(hue_factor, name='hue', center=0, bound=(-0.5, 0.5), non_negative=False)
-
- return adjust_hue(image, hue_factor)
-
-
- def random_crop(image, size, padding, pad_if_needed, fill, padding_mode):
- '''
-
- Parameters
- ----------
- image:
- Input images to crop and pad if needed.
- size:
- Desired output size of the crop. If size is an int instead of sequence like (h, w),
- a square crop (size, size) is made. If provided a sequence of length 1,
- it will be interpreted as (size[0], size[0]).
- padding:
- Optional, padding on each border of the image. Default is None.
- If a single int is provided this is used to pad all borders.
- If sequence of length 2 is provided this is the padding on left/right and top/bottom respectively.
- If a sequence of length 4 is provided this is the padding for the left, top, right and bottom borders respectively.
- pad_if_needed:
- It will pad the image if smaller than the desired size to avoid raising an exception.
- Since cropping is done after padding, the padding seems to be done at a random offset.
- fill:
- Pixel fill value for constant fill. Default is 0.
- padding_mode:
- Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant.
-
- Returns:
- cropped images.
- -------
-
- '''
- image = ops.convert_to_tensor(image, name='image')
- _AssertAtLeast3DImage(image)
-
- if isinstance(size, int):
- size = (size, size)
- elif isinstance(size, (tuple, list)) and len(size) == 2:
- size = size
- else:
- raise ValueError('Size should be a int or a list/tuple with length of 2. ' 'But got {}'.format(size))
-
- size = ops.convert_to_tensor(size, dtype=dtypes.int32, name='size')
- if padding is not None:
- image = pad(image, padding, fill, padding_mode)
-
- image_shape = image.get_shape()
- if image_shape.ndims == 3:
- height, width, channels = image_shape
- elif image_shape.ndims == 4:
- batch, height, width, channels = image_shape
-
- if pad_if_needed and height < size[0]:
- image = pad(image, (0, size[0] - height), fill, padding_mode)
- if pad_if_needed and width < size[1]:
- image = pad(image, (size[1] - width, 0), fill, padding_mode)
-
- image_shape = image.get_shape()
- if image_shape.ndims == 3:
- height, width, channels = image_shape
- elif image_shape.ndims == 4:
- batch, height, width, channels = image_shape
-
- target_height, target_width = size
- if height < target_height or width < target_width:
- raise ValueError(
- 'Crop size {} should be smaller than input image size {}. '.format(
- (target_height, target_width), (height, width)
- )
- )
-
- if target_height == height and target_width == width:
- return crop(image, 0, 0, target_height, target_width)
-
- offset_height = random_ops.random_uniform([], minval=0, maxval=height - target_height + 1, dtype=size.dtype)
-
- offset_width = random_ops.random_uniform([], minval=0, maxval=width - target_width + 1, dtype=size.dtype)
-
- return crop(image, offset_height, offset_width, target_height, target_width)
-
-
- def random_resized_crop(image, size, scale, ratio, interpolation):
- '''Crop the given image to random size and aspect ratio.
-
- Parameters
- ----------
- image:
- 4-D Tensor of shape [batch, height, width, channels] or 3-D Tensor of shape [height, width, channels].
- size:
- Target size of output image, with (height, width) shape. if size is int, target size will be (size, size).
- scale:
- Range of size of the origin size cropped. Default: (0.08, 1.0)
- ratio:
- Range of aspect ratio of the origin aspect ratio cropped. Default: (0.75, 1.33)
- interpolation:
- Interpolation method. Default: 'bilinear'.
-
- Returns:
- Randomly cropped and resized image.
- -------
-
- '''
-
- if isinstance(size, int):
- size = (size, size)
- elif isinstance(size, (list, tuple)) and len(size) == 2:
- size = size
- else:
- raise TypeError('Size should be a int or a list/tuple with length of 2.' 'But got {}.'.format(size))
- if not (isinstance(scale, (list, tuple)) and len(scale) == 2):
- raise TypeError('Scale should be a list/tuple with length of 2.' 'But got {}.'.format(scale))
- if not (isinstance(ratio, (list, tuple)) and len(ratio) == 2):
- raise TypeError('Scale should be a list/tuple with length of 2.' 'But got {}.'.format(ratio))
-
- if (scale[0] > scale[1]) or (ratio[0] > ratio[1]):
- raise ValueError("Scale and ratio should be of kind (min, max)")
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
-
- def get_param(image, scale, ratio):
- height, width = _get_image_size(image)
- area = math_ops.cast(height * width, dtype=dtypes.float32)
- ratio = ops.convert_to_tensor(ratio, dtype=dtypes.float32)
- log_ratio = math_ops.log(ratio)
- for _ in range(10):
- target_area = area * random_ops.random_uniform([], minval=scale[0], maxval=scale[1], dtype=dtypes.float32)
- aspect_ratio = math_ops.exp(
- random_ops.random_uniform([], minval=log_ratio[0], maxval=log_ratio[1], dtype=dtypes.float32)
- )
-
- target_width = math_ops.to_int32(math_ops.round(math_ops.sqrt(target_area * aspect_ratio)))
-
- target_height = math_ops.to_int32(math_ops.round(math_ops.sqrt(target_area / aspect_ratio)))
-
- if 0 < target_width <= width and 0 < target_height <= height:
- offset_height = random_ops.random_uniform(
- [], minval=0, maxval=height - target_height + 1, dtype=dtypes.int32
- )
-
- offset_width = random_ops.random_uniform(
- [], minval=0, maxval=width - target_width + 1, dtype=dtypes.int32
- )
-
- return offset_height, offset_width, target_height, target_width
-
- height = ops.convert_to_tensor(height, dtype=dtypes.float32)
- width = ops.convert_to_tensor(width, dtype=dtypes.float32)
- in_ratio = width / height
- if in_ratio < ratio[0]:
- target_width = width
- target_height = math_ops.to_int32(math_ops.round(target_width / ratio[0]))
- elif in_ratio > ratio[1]:
- target_height = height
- target_width = math_ops.to_int32(math_ops.round(target_height / ratio[1]))
- else:
- target_height = height
- target_width = width
- offset_height = (height - target_height) // 2
- offset_width = (width - target_width) // 2
- return offset_height, offset_width, target_height, target_width
-
- offset_height, offset_width, target_heigth, target_width = get_param(image, scale, ratio)
- image = crop(image, offset_height, offset_width, target_heigth, target_width)
- image = resize(image, size, interpolation)
- return image
-
-
- def random_vflip(image, prob):
- '''Vertically flip the input image randomly with a given probability.
-
- Parameters
- ----------
- image:
- 4-D Tensor of shape [batch, height, width, channels] or 3-D Tensor of shape [height, width, channels].
- prob:
- probability of the image being flipped. Default value is 0.5
- Returns:
- A tensor of the same type and shape as image.
- -------
-
- '''
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
- random_prob = random_ops.random_uniform([], minval=0, maxval=1.0, dtype=dtypes.float32)
- flip_flag = math_ops.less(random_prob, prob)
- if flip_flag:
- return vflip(image)
- return image
-
-
- def random_hflip(image, prob):
- '''horizontally flip the input image randomly with a given probability.
-
- Parameters
- ----------
- image:
- 4-D Tensor of shape [batch, height, width, channels] or 3-D Tensor of shape [height, width, channels].
- prob:
- probability of the image being flipped. Default value is 0.5
- Returns:
- A tensor of the same type and shape as image.
- -------
-
- '''
- image = ops.convert_to_tensor(image, name='image')
- image = _AssertAtLeast3DImage(image)
- random_prob = random_ops.random_uniform([], minval=0, maxval=1.0, dtype=dtypes.float32)
- flip_flag = math_ops.less(random_prob, prob)
- if flip_flag:
- return hflip(image)
- return image
-
-
- def random_rotation(image, degrees, interpolation, expand, center, fill):
- '''Rotate the image by angle.
-
- Parameters
- ----------
- image:
- Input tensor. Must be 3D.
- degrees:
- Range of degrees to select from.If degrees is a number instead of sequence like (min, max), the range of degrees
- will be (-degrees, +degrees).
- interpolation:
- Points outside the boundaries of the input are filled according to the given mode
- (one of {'nearest', 'bilinear'}).
- expand:
- Optional expansion flag.
- If true, expands the output to make it large enough to hold the entire rotated image.
- If false or omitted, make the output image the same size as the input image.
- Note that the expand flag assumes rotation around the center and no translation.
- center:
- Optional center of rotation, (x, y). Origin is the upper left corner.
- Default is the center of the image.
- fill:
- Pixel fill value for the area outside the rotated image.
- Default is ``0``. If given a number, the value is used for all bands respectively.
-
- Returns:
- Rotated image tensor.
- -------
-
- '''
- if isinstance(image, (tf.Tensor, np.ndarray)) and len(image.shape) == 3:
- image = np.asarray(image)
- else:
- 'Image should be a 3d tensor or np.ndarray.'
- h, w, c = image.shape[0], image.shape[1], image.shape[2]
-
- if isinstance(degrees, numbers.Number):
- if degrees < 0:
- raise ValueError('If degrees is a single number, it must be positive.' 'But got {}'.format(degrees))
- degrees = (-degrees, degrees)
- elif not (isinstance(degrees, (list, tuple)) and len(degrees) == 2):
- raise ValueError('If degrees is a list/tuple, it must be length of 2.' 'But got {}'.format(degrees))
- else:
- if degrees[0] > degrees[1]:
- raise ValueError('if degrees is a list/tuple, it should be (min, max).')
-
- if isinstance(fill, numbers.Number):
- fill = (fill, ) * c
- elif not (isinstance(fill, (list, tuple)) and len(fill) == c):
- raise ValueError(
- 'If fill should be a single number or a list/tuple with length of image channels.'
- 'But got {}'.format(fill)
- )
-
- if interpolation not in ('nearest', 'bilinear'):
- raise ValueError('Interpolation only support {\'nearest\', \'bilinear\'} .')
-
- orig_dtype = image.dtype
- image = np.asarray(image, dtype=np.float)
- theta = np.random.uniform(degrees[0], degrees[1])
- angle = -math.radians(theta)
- rotation_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
-
- if center is None:
- rotn_center = (w / 2.0, h / 2.0)
- else:
- rotn_center = center
-
- matrix = [
- round(math.cos(angle), 15),
- round(math.sin(angle), 15),
- 0.0,
- round(-math.sin(angle), 15),
- round(math.cos(angle), 15),
- 0.0,
- ]
-
- def transform(x, y, matrix):
- (a, b, c, d, e, f) = matrix
- return a * x + b * y + c, d * x + e * y + f
-
- matrix[2], matrix[5] = transform(-rotn_center[0] - 0, -rotn_center[1] - 0, matrix)
- matrix[2] += rotn_center[0]
- matrix[5] += rotn_center[1]
-
- if expand:
- # calculate output size
- xx = []
- yy = []
- for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
- x, y = transform(x, y, matrix)
- xx.append(x)
- yy.append(y)
- nw = math.ceil(max(xx)) - math.floor(min(xx))
- nh = math.ceil(max(yy)) - math.floor(min(yy))
- matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix)
- w, h = nw, nh
-
- image = np.rollaxis(image, 2, 0)
- dummy = np.ones((1, image.shape[1], image.shape[2]), dtype=image.dtype)
- image = np.concatenate((image, dummy), axis=0)
- final_offset = np.array([matrix[5], matrix[2]])
-
- channel_images = [
- ndimage.interpolation.affine_transform(
- x_channel, rotation_matrix, final_offset, output_shape=(h, w), order=3, mode='constant', cval=0
- ) for x_channel in image
- ]
- image = np.stack(channel_images, axis=0)
- image = np.rollaxis(image, 0, 3)
- mask = image[:, :, -1:]
- image = image[:, :, :-1]
- mask = np.tile(mask, (1, 1, image.shape[2]))
- fill = np.tile(fill, (image.shape[0], image.shape[1], 1))
- if interpolation == 'nearest':
- mask = mask < 0.5
- image[mask] = fill[mask]
- else:
- image = image * mask + (1.0 - mask) * fill
- image = np.asarray(image, dtype=orig_dtype)
- image = ops.convert_to_tensor(image)
- return image
-
-
- def transform_matrix_offset_center(matrix, x, y):
- o_x = float(x) / 2 + 0.5
- o_y = float(y) / 2 + 0.5
- offset_matrix = np.array([[1, 0, o_x], [0, 1, o_y], [0, 0, 1]])
- reset_matrix = np.array([[1, 0, -o_x], [0, 1, -o_y], [0, 0, 1]])
- transform_matrix = np.dot(np.dot(offset_matrix, matrix), reset_matrix)
- return transform_matrix
-
-
- def random_shear(image, degrees, interpolation, fill):
-
- if isinstance(image, (tf.Tensor, np.ndarray)) and len(image.shape) == 3:
- image = np.asarray(image)
- else:
- 'Image should be a 3d tensor or np.ndarray.'
- h, w, c = image.shape[0], image.shape[1], image.shape[2]
-
- if interpolation not in ('nearest', 'bilinear'):
- raise ValueError('Interpolation only support {\'nearest\', \'bilinear\'} .')
-
- if isinstance(degrees, numbers.Number):
- degrees = (-degrees, degrees, 0, 0)
- elif isinstance(degrees, (list, tuple)) and (len(degrees) == 2 or len(degrees) == 4):
- if len(degrees) == 2:
- degrees = (degrees[0], degrees[1], 0, 0)
- else:
- raise ValueError(
- 'degrees should be a single number or a list/tuple with length in (2 ,4).'
- 'But got {}'.format(degrees)
- )
-
- if isinstance(fill, numbers.Number):
- fill = (fill, ) * c
- elif not (isinstance(fill, (list, tuple)) and len(fill) == c):
- raise ValueError(
- 'If fill should be a single number or a list/tuple with length of image channels.'
- 'But got {}'.format(fill)
- )
-
- orig_dtype = image.dtype
- image = np.asarray(image, dtype=np.float)
- shear = [np.random.uniform(degrees[0], degrees[1]), np.random.uniform(degrees[2], degrees[3])]
- shear = np.deg2rad(shear)
- shear_matrix = np.array(
- [[math.cos(shear[1]), math.sin(shear[1]), 0], [math.sin(shear[0]), math.cos(shear[0]), 0], [0, 0, 1]]
- )
- transform_matrix = shear_matrix
- transform_matrix = transform_matrix_offset_center(transform_matrix, h, w)
-
- shear_matrix = transform_matrix[:2, :2]
- offset = transform_matrix[:2, 2]
- image = np.rollaxis(image, 2, 0)
-
- dummy = np.ones((1, image.shape[1], image.shape[2]), dtype=image.dtype)
- image = np.concatenate((image, dummy), axis=0)
-
- channel_images = [
- ndimage.interpolation.affine_transform(x_channel, shear_matrix, offset, order=3, mode='constant', cval=0)
- for x_channel in image
- ]
-
- image = np.stack(channel_images, axis=0)
- image = np.rollaxis(image, 0, 3)
- mask = image[:, :, -1:]
- image = image[:, :, :-1]
- mask = np.tile(mask, (1, 1, c))
- fill = np.tile(fill, (h, w, 1))
- if interpolation == 'nearest':
- mask = mask < 0.5
- image[mask] = fill[mask]
- else:
- image = image * mask + (1.0 - mask) * fill
- image = np.asarray(image, dtype=orig_dtype)
- image = ops.convert_to_tensor(image)
- return image
-
-
- def random_shift(image, shift, interpolation, fill):
-
- if isinstance(image, (tf.Tensor, np.ndarray)) and len(image.shape) == 3:
- image = np.asarray(image)
- else:
- 'Image should be a 3d tensor or np.ndarray.'
- h, w, c = image.shape[0], image.shape[1], image.shape[2]
-
- if interpolation not in ('nearest', 'bilinear'):
- raise ValueError('Interpolation only support {\'nearest\', \'bilinear\'} .')
-
- if not (isinstance(shift, (tuple, list)) and len(shift) == 2):
-
- raise ValueError('Shift should be a list/tuple with length of 2.' 'But got {}'.format(shift))
-
- if isinstance(fill, numbers.Number):
- fill = (fill, ) * c
- elif not (isinstance(fill, (list, tuple)) and len(fill) == c):
- raise ValueError(
- 'If fill should be a single number or a list/tuple with length of image channels.'
- 'But got {}'.format(fill)
- )
-
- orig_dtype = image.dtype
- image = np.asarray(image, dtype=np.float)
- hrg = shift[0]
- wrg = shift[1]
- tx = -np.random.uniform(-hrg, hrg) * w
- ty = -np.random.uniform(-wrg, wrg) * h
-
- shift_matrix = np.array([[1, 0, tx], [0, 1, ty], [0, 0, 1]])
-
- transform_matrix = transform_matrix_offset_center(shift_matrix, h, w)
- shift_matrix = transform_matrix[:2, :2]
- offset = transform_matrix[:2, 2]
- image = np.rollaxis(image, 2, 0)
-
- dummy = np.ones((1, image.shape[1], image.shape[2]), dtype=image.dtype)
- image = np.concatenate((image, dummy), axis=0)
-
- channel_images = [
- ndimage.interpolation.affine_transform(x_channel, shift_matrix, offset, order=3, mode='constant', cval=0)
- for x_channel in image
- ]
-
- image = np.stack(channel_images, axis=0)
- image = np.rollaxis(image, 0, 3)
- mask = image[:, :, -1:]
- image = image[:, :, :-1]
- mask = np.tile(mask, (1, 1, c))
- fill = np.tile(fill, (h, w, 1))
- if interpolation == 'nearest':
- mask = mask < 0.5
- image[mask] = fill[mask]
- else:
- image = image * mask + (1.0 - mask) * fill
- image = np.asarray(image, dtype=orig_dtype)
- image = ops.convert_to_tensor(image)
- return image
-
-
- def random_zoom(image, zoom, interpolation, fill):
-
- if isinstance(image, (tf.Tensor, np.ndarray)) and len(image.shape) == 3:
- image = np.asarray(image)
- else:
- 'Image should be a 3d tensor or np.ndarray.'
- h, w, c = image.shape[0], image.shape[1], image.shape[2]
-
- if interpolation not in ('nearest', 'bilinear'):
- raise ValueError('Interpolation only support {\'nearest\', \'bilinear\'} .')
-
- if not (isinstance(zoom, (tuple, list)) and len(zoom) == 2):
-
- raise ValueError('Zoom should be a list/tuple with length of 2.' 'But got {}'.format(zoom))
- if not (0 <= zoom[0] <= zoom[1]):
-
- raise ValueError('Zoom values should be positive, and zoom[1] should be greater than zoom[0].')
-
- if isinstance(fill, numbers.Number):
- fill = (fill, ) * c
- elif not (isinstance(fill, (list, tuple)) and len(fill) == c):
- raise ValueError(
- 'If fill should be a single number or a list/tuple with length of image channels.'
- 'But got {}'.format(fill)
- )
-
- orig_dtype = image.dtype
- image = np.asarray(image, dtype=np.float)
- zoom_factor = 1 / np.random.uniform(zoom[0], zoom[1])
- zoom_matrix = np.array([[zoom_factor, 0, 0], [0, zoom_factor, 0], [0, 0, 1]])
- transform_matrix = transform_matrix_offset_center(zoom_matrix, h, w)
- zoom_matrix = transform_matrix[:2, :2]
- offset = transform_matrix[:2, 2]
-
- image = np.rollaxis(image, 2, 0)
-
- dummy = np.ones((1, image.shape[1], image.shape[2]), dtype=image.dtype)
- image = np.concatenate((image, dummy), axis=0)
-
- channel_images = [
- ndimage.interpolation.affine_transform(x_channel, zoom_matrix, offset, order=3, mode='constant', cval=0)
- for x_channel in image
- ]
-
- image = np.stack(channel_images, axis=0)
- image = np.rollaxis(image, 0, 3)
- mask = image[:, :, -1:]
- image = image[:, :, :-1]
- mask = np.tile(mask, (1, 1, c))
- fill = np.tile(fill, (h, w, 1))
- if interpolation == 'nearest':
- mask = mask < 0.5
- image[mask] = fill[mask]
- else:
- image = image * mask + (1.0 - mask) * fill
- image = np.asarray(image, dtype=orig_dtype)
- image = ops.convert_to_tensor(image)
- return image
-
-
- def random_affine(image, degrees, shift, zoom, shear, interpolation, fill):
-
- if isinstance(image, (tf.Tensor, np.ndarray)) and len(image.shape) == 3:
- image = np.asarray(image)
- else:
- 'Image should be a 3d tensor or np.ndarray.'
- h, w, c = image.shape[0], image.shape[1], image.shape[2]
-
- if isinstance(fill, numbers.Number):
- fill = (fill, ) * c
- elif not (isinstance(fill, (list, tuple)) and len(fill) == c):
- raise ValueError(
- 'If fill should be a single number or a list/tuple with length of image channels.'
- 'But got {}'.format(fill)
- )
- orig_dtype = image.dtype
- image = np.asarray(image, dtype=np.float)
- theta = np.random.uniform(degrees[0], degrees[1])
- theta = np.deg2rad(theta)
- rotation_matrix = np.array([[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], [0, 0, 1]])
- transform_matrix = rotation_matrix
-
- if shift is not None:
- max_dx = float(shift[0] * w)
- max_dy = float(shift[1] * h)
- tx = -int(round(np.random.uniform(-max_dx, max_dx)))
- ty = -int(round(np.random.uniform(-max_dy, max_dy)))
- shift_matrix = np.array([[1, 0, tx], [0, 1, ty], [0, 0, 1]])
- transform_matrix = np.dot(transform_matrix, shift_matrix)
-
- if shear is not None:
- shear_x = shear_y = 0
- shear_x = float(np.random.uniform(shear[0], shear[1]))
- if len(shear) == 4:
- shear_y = float(np.random.uniform(shear[2], shear[3]))
- shear_x = np.deg2rad(shear_x)
- shear_y = np.deg2rad(shear_y)
- shear_matrix = np.array(
- [[math.cos(shear_y), math.sin(shear_y), 0], [math.sin(shear_x), math.cos(shear_x), 0], [0, 0, 1]]
- )
- transform_matrix = np.dot(transform_matrix, shear_matrix)
-
- if zoom is not None:
- zoom = 1 / float(np.random.uniform(zoom[0], zoom[1]))
- zoom_matrix = np.array([[zoom, 0, 0], [0, zoom, 0], [0, 0, 1]])
-
- transform_matrix = np.dot(transform_matrix, zoom_matrix)
-
- transform_matrix = transform_matrix_offset_center(transform_matrix, h, w)
- image = np.rollaxis(image, 2, 0)
- finale_affine_matrix = transform_matrix[:2, :2]
- finale_offset = transform_matrix[:2, 2]
- dummy = np.ones((1, h, w), dtype=image.dtype)
- image = np.concatenate((image, dummy), axis=0)
-
- channel_images = [
- ndimage.interpolation.affine_transform(
- x_channel, finale_affine_matrix, finale_offset, order=3, mode='constant', cval=0
- ) for x_channel in image
- ]
-
- image = np.stack(channel_images, axis=0)
- image = np.rollaxis(image, 0, 3)
- mask = image[:, :, -1:]
- image = image[:, :, :-1]
- mask = np.tile(mask, (1, 1, c))
- fill = np.tile(fill, (h, w, 1))
- if interpolation == 'nearest':
- mask = mask < 0.5
- image[mask] = fill[mask]
- else:
- image = image * mask + (1.0 - mask) * fill
- image = np.asarray(image, dtype=orig_dtype)
- image = ops.convert_to_tensor(image)
- return image
|