|
- # Copyright 2022 Huawei Technologies Co., Ltd
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """
- Image luminance.
- """
- import math
- import numpy as np
- import cv2
-
- from mindarmour.natural_robustness.transform.image.natural_perturb import _NaturalPerturb
- from mindarmour.utils._check_param import check_param_multi_types, check_param_in_range, check_param_type, \
- check_value_non_negative
- from mindarmour.utils.logger import LogUtil
-
- LOGGER = LogUtil.get_instance()
- TAG = 'Image Luminance'
-
-
- class Contrast(_NaturalPerturb):
- r"""
- Contrast of an image.
-
- Args:
- alpha (Union[float, int]): Control the contrast of an image. :math:`out\_image = in\_image \times alpha+beta`.
- Suggested value range in [0.2, 2]. Default: 1.
- beta (Union[float, int]): Delta added to alpha. Default: 0.
- auto_param (bool): Auto selected parameters. Selected parameters will preserve semantics of image.
- Default: False.
-
- Examples:
- >>> import cv2
- >>> img = cv2.imread('1.png')
- >>> img = np.array(img)
- >>> alpha = 0.1
- >>> beta = 1
- >>> trans = Contrast(alpha, beta)
- >>> dst = trans(img)
- """
-
- def __init__(self, alpha=1, beta=0, auto_param=False):
- super(Contrast, self).__init__()
- self.alpha = check_param_multi_types('factor', alpha, [int, float])
- self.beta = check_param_multi_types('factor', beta, [int, float])
- auto_param = check_param_type('auto_param', auto_param, bool)
- if auto_param:
- self.alpha = np.random.uniform(0.2, 2)
- self.beta = np.random.uniform(-20, 20)
-
- def __call__(self, image):
- """
- Transform the image.
-
- Args:
- image (numpy.ndarray): Original image to be transformed.
-
- Returns:
- numpy.ndarray, transformed image.
- """
- ori_dtype = image.dtype
- _, chw, normalized, gray3dim, image = self._check(image)
- dst = cv2.convertScaleAbs(image, alpha=self.alpha, beta=self.beta)
- dst = self._original_format(dst, chw, normalized, gray3dim)
- return dst.astype(ori_dtype)
-
-
- def _circle_gradient_mask(img_src, color_start, color_end, scope=0.5, point=None):
- """
- Generate circle gradient mask.
-
- Args:
- img_src (numpy.ndarray): Source image.
- color_start (union([tuple, list])): Color of circle gradient center.
- color_end (union([tuple, list])): Color of circle gradient edge.
- scope (float): Range of the gradient. A larger value indicates a larger gradient range.
- point (union([tuple, list]): Gradient center point.
-
- Returns:
- numpy.ndarray, gradients mask.
- """
- if not isinstance(img_src, np.ndarray):
- raise TypeError('`src` must be numpy.ndarray type, but got {0}.'.format(type(img_src)))
-
- shape = img_src.shape
- height, width = shape[:2]
- rgb = False
- if len(shape) == 3:
- rgb = True
- if point is None:
- point = (height // 2, width // 2)
- x, y = point
-
- # upper left
- bound_upper_left = math.ceil(math.sqrt(x ** 2 + y ** 2))
- # upper right
- bound_upper_right = math.ceil(math.sqrt(height ** 2 + (width - y) ** 2))
- # lower left
- bound_lower_left = math.ceil(math.sqrt((height - x) ** 2 + y ** 2))
- # lower right
- bound_lower_right = math.ceil(math.sqrt((height - x) ** 2 + (width - y) ** 2))
-
- radius = max(bound_lower_left, bound_lower_right, bound_upper_left, bound_upper_right) * scope
-
- img_grad = np.ones_like(img_src, dtype=np.uint8) * max(color_end)
- # opencv use BGR format
- grad_b = float(color_end[0] - color_start[0]) / radius
- grad_g = float(color_end[1] - color_start[1]) / radius
- grad_r = float(color_end[2] - color_start[2]) / radius
-
- for i in range(height):
- for j in range(width):
- distance = math.ceil(math.sqrt((x - i) ** 2 + (y - j) ** 2))
- if distance >= radius:
- continue
- if rgb:
- img_grad[i, j, 0] = color_start[0] + distance * grad_b
- img_grad[i, j, 1] = color_start[1] + distance * grad_g
- img_grad[i, j, 2] = color_start[2] + distance * grad_r
- else:
- img_grad[i, j] = color_start[0] + distance * grad_b
-
- return img_grad.astype(np.uint8)
-
-
- def _line_gradient_mask(image, start_pos=None, start_color=(0, 0, 0), end_color=(255, 255, 255), mode='horizontal'):
- """
- Generate liner gradient mask.
-
- Args:
- image (numpy.ndarray): Original image.
- start_pos (union[tuple, list]): 2D coordinate of gradient center.
- start_color (union([tuple, list])): Color of circle gradient center.
- end_color (union([tuple, list])): Color of circle gradient edge.
- mode (str): Direction of gradient. Optional value is 'vertical' or 'horizontal'.
-
- Returns:
- numpy.ndarray, gradients mask.
- """
- shape = image.shape
- h, w = shape[:2]
- rgb = False
- if len(shape) == 3:
- rgb = True
- if start_pos is None:
- start_pos = 0.5
- else:
- if mode == 'horizontal':
- if start_pos[0] > h:
- start_pos = 1
- else:
- start_pos = start_pos[0] / h
- else:
- if start_pos[1] > w:
- start_pos = 1
- else:
- start_pos = start_pos[1] / w
- start_color = np.array(start_color)
- end_color = np.array(end_color)
- if mode == 'horizontal':
- w_l = int(w * start_pos)
- w_r = w - w_l
- if w_l > w_r:
- r_end_color = (end_color - start_color) / start_pos * (1 - start_pos) + start_color
- left = np.linspace(end_color, start_color, w_l)
- right = np.linspace(start_color, r_end_color, w_r)
- else:
- l_end_color = (end_color - start_color) / (1 - start_pos) * start_pos + start_color
- left = np.linspace(l_end_color, start_color, w_l)
- right = np.linspace(start_color, end_color, w_r)
- line = np.concatenate((left, right), axis=0)
- mask = np.reshape(np.tile(line, (h, 1)), (h, w, 3))
- else:
- # 'vertical'
- h_t = int(h * start_pos)
- h_b = h - h_t
- if h_t > h_b:
- b_end_color = (end_color - start_color) / start_pos * (1 - start_pos) + start_color
- top = np.linspace(end_color, start_color, h_t)
- bottom = np.linspace(start_color, b_end_color, h_b)
- else:
- t_end_color = (end_color - start_color) / (1 - start_pos) * start_pos + start_color
- top = np.linspace(t_end_color, start_color, h_t)
- bottom = np.linspace(start_color, end_color, h_b)
- line = np.concatenate((top, bottom), axis=0)
- mask = np.reshape(np.tile(line, (w, 1)), (w, h, 3))
- mask = np.transpose(mask, [1, 0, 2])
- if not rgb:
- mask = mask[:, :, 0]
- return mask.astype(np.uint8)
-
-
- class GradientLuminance(_NaturalPerturb):
- """
- Gradient adjusts the luminance of picture.
-
- Args:
- color_start (union[tuple, list]): Color of gradient center. Default:(0, 0, 0).
- color_end (union[tuple, list]): Color of gradient edge. Default:(255, 255, 255).
- start_point (union[tuple, list]): 2D coordinate of gradient center.
- scope (float): Range of the gradient. A larger value indicates a larger gradient range. Default: 0.5.
- pattern (str): Dark or light, this value must be in ['light', 'dark']. Default: 'light'.
- bright_rate (float): Control brightness. A larger value indicates a larger gradient range. If parameter
- 'pattern' is 'light', Suggested value range in [0.1, 0.7], if parameter 'pattern' is 'dark', Suggested value
- range in [0.1, 0.9]. Default: 0.3.
- mode (str): Gradient mode, value must be in ['circle', 'horizontal', 'vertical']. Default: 'circle'.
- auto_param (bool): Auto selected parameters. Selected parameters will preserve semantics of image.
- Default: False.
-
- Examples:
- >>> import cv2
- >>> img = cv2.imread('x.png')
- >>> height, width = img.shape[:2]
- >>> point = (height // 4, width // 2)
- >>> start = (255, 255, 255)
- >>> end = (0, 0, 0)
- >>> scope = 0.3
- >>> pattern='light'
- >>> bright_rate = 0.3
- >>> trans = GradientLuminance(start, end, point, scope, pattern, bright_rate, mode='circle')
- >>> img_new = trans(img)
- """
-
- def __init__(self, color_start=(0, 0, 0), color_end=(255, 255, 255), start_point=(10, 10), scope=0.5,
- pattern='light', bright_rate=0.3, mode='circle', auto_param=False):
- super(GradientLuminance, self).__init__()
- self.color_start = check_param_multi_types('color_start', color_start, [list, tuple])
- self.color_end = check_param_multi_types('color_end', color_end, [list, tuple])
- self.start_point = check_param_multi_types('start_point', start_point, [list, tuple])
- self.scope = check_value_non_negative('scope', scope)
- self.bright_rate = check_param_type('bright_rate', bright_rate, float)
- self.bright_rate = check_param_in_range('bright_rate', bright_rate, 0, 1)
- self.auto_param = check_param_type('auto_param', auto_param, bool)
-
- if pattern in ['light', 'dark']:
- self.pattern = pattern
- else:
- msg = "Value of param pattern must be in ['light', 'dark']"
- LOGGER.error(TAG, msg)
- raise ValueError(msg)
- if mode in ['circle', 'horizontal', 'vertical']:
- self.mode = mode
- else:
- msg = "Value of param mode must be in ['circle', 'horizontal', 'vertical']"
- LOGGER.error(TAG, msg)
- raise ValueError(msg)
-
- def _set_auto_param(self, w, h):
- self.color_start = (np.random.uniform(0, 255),) * 3
- self.color_end = (np.random.uniform(0, 255),) * 3
- self.start_point = (np.random.uniform(0, w), np.random.uniform(0, h))
- self.scope = np.random.uniform(0, 1)
- self.bright_rate = np.random.uniform(0.1, 0.9)
- self.pattern = np.random.choice(['light', 'dark'])
- self.mode = np.random.choice(['circle', 'horizontal', 'vertical'])
-
- def __call__(self, image):
- """
- Gradient adjusts the luminance of picture.
-
- Args:
- image (numpy.ndarray): Original image.
-
- Returns:
- numpy.ndarray, image with perlin noise.
- """
- ori_dtype = image.dtype
- _, chw, normalized, gray3dim, image = self._check(image)
- w, h = image.shape[:2]
- if self.auto_param:
- self._set_auto_param(w, h)
- if self.mode == 'circle':
- mask = _circle_gradient_mask(image, self.color_start, self.color_end, self.scope, self.start_point)
- else:
- mask = _line_gradient_mask(image, self.start_point, self.color_start, self.color_end, mode=self.mode)
-
- if self.pattern == 'light':
- img_new = cv2.addWeighted(image, 1, mask, self.bright_rate, 0.0)
- else:
- img_new = cv2.addWeighted(image, self.bright_rate, mask, 1 - self.bright_rate, 0.0)
- img_new = self._original_format(img_new, chw, normalized, gray3dim)
- return img_new.astype(ori_dtype)
|