# 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 ``'light'`` or ``'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 ``'circle'``, ``'horizontal'`` or ``'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)