Browse Source

add natural robustness methods

pull/275/head
lvzhangcheng 3 years ago
parent
commit
da807797e1
2 changed files with 926 additions and 0 deletions
  1. +16
    -0
      mindarmour/natural_robustness/__init__.py
  2. +910
    -0
      mindarmour/natural_robustness/natural_noise.py

+ 16
- 0
mindarmour/natural_robustness/__init__.py View File

@@ -0,0 +1,16 @@
# Copyright 2021 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.
"""
This package include methods to generate natural perturbation samples.
"""

+ 910
- 0
mindarmour/natural_robustness/natural_noise.py View File

@@ -0,0 +1,910 @@
# Copyright 2019 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 transform
"""
import os
import math
import numpy as np
import cv2
from perlin_numpy import generate_fractal_noise_2d
from mindarmour.utils._check_param import check_param_multi_types, check_param_in_range, check_numpy_param, \
check_int_positive, check_param_type, check_value_non_negative
from mindarmour.utils.logger import LogUtil
LOGGER = LogUtil.get_instance()
TAG = 'Image Transformation'
class Contrast:
"""
Contrast of an image.
Args:
alpha(Union[float, int]): Control the contrast of an image. If 1.0,
gives the original image. If 0, gives a gray image. Default: 1.
beta(Union[float, int]): delta added to alpha. Default: 0.
"""
def __init__(self, alpha=1, beta=0):
super(Contrast, self).__init__()
self.set_params(alpha)
self.beta = check_param_multi_types('factor', beta, [int, float])
def set_params(self, alpha=1, auto_param=False):
"""
Set contrast parameters.
Args:
alpha (Union[float, int]): Control the contrast of an image. If 1.0
gives the original image. If 0 gives a gray image. Default: 1.
auto_param (bool): True if auto generate parameters. Default: False.
"""
if auto_param:
self.alpha = np.random.uniform(-5, 5)
else:
self.alpha = check_param_multi_types('factor', alpha, [int, float])
def __call__(self, image):
"""
Transform the image.
Args:
image (numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
image = check_numpy_param('image', image)
new_img = cv2.convertScaleAbs(image, alpha=self.alpha, beta=self.beta)
return new_img
class GaussianBlur:
"""
Blurs the image using Gaussian blur filter.
Args:
ksize(Union[list, tuple]): Blur radius, 0 means no blur. Default: 0.
"""
def __init__(self, ksize=(5, 5)):
super(GaussianBlur, self).__init__()
ksize = check_param_multi_types('ksize', ksize, [list, tuple])
self.ksize = tuple(ksize)
def __call__(self, image):
"""
Transform the image.
Args:
image (numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
image = check_numpy_param('image', image)
new_img = cv2.GaussianBlur(image, self.ksize, 0)
return new_img
class SaltAndPepperNoise:
"""
Add noise of an image.
Args:
factor (float): factor is the ratio of pixels to add noise.
If 0 gives the original image. Default 0.
"""
def __init__(self, factor=0):
super(SaltAndPepperNoise, self).__init__()
self.set_params(factor)
def set_params(self, factor=0, auto_param=False):
"""
Set noise parameters.
Args:
factor (Union[float, int]): factor is the ratio of pixels to
add noise. If 0 gives the original image. Default 0.
auto_param (bool): True if auto generate parameters. Default: False.
"""
if auto_param:
self.factor = np.random.uniform(0, 1)
else:
self.factor = check_param_multi_types('factor', factor, [int, float])
def __call__(self, image):
"""
Transform the image.
Args:
image (numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
image = check_numpy_param('image', image)
ori_dtype = image.dtype
noise = np.random.uniform(low=-1, high=1, size=(image.shape[0], image.shape[1]))
trans_image = np.copy(image)
threshold = 1 - self.factor
trans_image[noise < -threshold] = (0, 0, 0)
trans_image[noise > threshold] = (255, 255, 255)
return trans_image.astype(ori_dtype)
class Translate:
"""
Translate an image.
Args:
x_bias (Union[int, float]): X-direction translation, x = x + x_bias*image_length.
Default: 0.
y_bias (Union[int, float]): Y-direction translation, y = y + y_bias*image_wide.
Default: 0.
"""
def __init__(self, x_bias=0, y_bias=0):
super(Translate, self).__init__()
self.set_params(x_bias, y_bias)
def set_params(self, x_bias=0, y_bias=0, auto_param=False):
"""
Set translate parameters.
Args:
x_bias (Union[float, int]): X-direction translation, and x_bias should be in range of (-1, 1). Default: 0.
y_bias (Union[float, int]): Y-direction translation, and y_bias should be in range of (-1, 1). Default: 0.
auto_param (bool): True if auto generate parameters. Default: False.
"""
x_bias = check_param_in_range('x_bias', x_bias, -1, 1)
y_bias = check_param_in_range('y_bias', y_bias, -1, 1)
self.auto_param = auto_param
if auto_param:
self.x_bias = np.random.uniform(-0.3, 0.3)
self.y_bias = np.random.uniform(-0.3, 0.3)
else:
self.x_bias = check_param_multi_types('x_bias', x_bias, [int, float])
self.y_bias = check_param_multi_types('y_bias', y_bias, [int, float])
def __call__(self, image):
"""
Transform the image.
Args:
image(numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
image = check_numpy_param('image', image)
h, w = image.shape[:2]
matrix = np.array([[1, 0, self.x_bias * w], [0, 1, self.y_bias * h]], dtype=np.float)
new_img = cv2.warpAffine(image, matrix, (w, h))
return new_img
class Scale:
"""
Scale an image in the middle.
Args:
factor_x (Union[float, int]): Rescale in X-direction, x=factor_x*x.
Default: 1.
factor_y (Union[float, int]): Rescale in Y-direction, y=factor_y*y.
Default: 1.
"""
def __init__(self, factor_x=1, factor_y=1):
super(Scale, self).__init__()
self.set_params(factor_x, factor_y)
def set_params(self, factor_x=1, factor_y=1, auto_param=False):
"""
Set scale parameters.
Args:
factor_x (Union[float, int]): Rescale in X-direction, x=factor_x*x.
Default: 1.
factor_y (Union[float, int]): Rescale in Y-direction, y=factor_y*y.
Default: 1.
auto_param (bool): True if auto generate parameters. Default: False.
"""
if auto_param:
self.factor_x = np.random.uniform(0.7, 3)
self.factor_y = np.random.uniform(0.7, 3)
else:
self.factor_x = check_param_multi_types('factor_x', factor_x, [int, float])
self.factor_y = check_param_multi_types('factor_y', factor_y, [int, float])
def __call__(self, image):
"""
Transform the image.
Args:
image(numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
image = check_numpy_param('image', image)
h, w = image.shape[:2]
matrix = np.array([[self.factor_x, 0, 0], [0, self.factor_y, 0]], dtype=np.float)
new_img = cv2.warpAffine(image, matrix, (w, h))
return new_img
class Shear:
"""
Shear an image, for each pixel (x, y) in the sheared image, the new value is
taken from a position (x+factor_x*y, factor_y*x+y) in the origin image. Then
the sheared image will be rescaled to fit original size.
Args:
factor_x (Union[float, int]): Shear factor of horizontal direction.
Default: 0.
factor_y (Union[float, int]): Shear factor of vertical direction.
Default: 0.
"""
def __init__(self, factor, director='horizonal'):
super(Shear, self).__init__()
self.factor = factor
self.director = director
def __call__(self, image):
"""
Transform the image.
Args:
image(numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
h, w = image.shape[:2]
if self.director == 'horizontal':
matrix = np.array([[1, self.factor, 0], [0, 1, 0]], dtype=np.float)
nw = int(w + self.factor * h)
nh = h
else:
matrix = np.array([[1, 0, 0], [self.factor, 1, 0]], dtype=np.float)
nw = w
nh = int(h + self.factor * w)
new_img = cv2.warpAffine(image, matrix, (nw, nh))
new_img = cv2.resize(new_img, (w, h))
return new_img
class Rotate:
"""
Rotate an image of degrees counter clockwise around its center.
Args:
angle(Union[float, int]): Degrees counter clockwise. Default: 0.
"""
def __init__(self, angle=0):
super(Rotate, self).__init__()
self.set_params(angle)
def set_params(self, angle=0, auto_param=False):
"""
Set rotate parameters.
Args:
angle(Union[float, int]): Degrees counter clockwise. Default: 0.
auto_param (bool): True if auto generate parameters. Default: False.
"""
if auto_param:
self.angle = np.random.uniform(0, 360)
else:
self.angle = check_param_multi_types('angle', angle, [int, float])
def __call__(self, image):
"""
Transform the image.
Args:
image(numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, rotated image.
"""
image = check_numpy_param('image', image)
h, w = image.shape[:2]
center = (w // 2, h // 2)
matrix = cv2.getRotationMatrix2D(center, -self.angle, 1.0)
cos = np.abs(matrix[0, 0])
sin = np.abs(matrix[0, 1])
# 计算图像旋转后的新边界
nw = int((h * sin) + (w * cos))
nh = int((h * cos) + (w * sin))
# 调整旋转矩阵的移动距离(t_{x}, t_{y})
matrix[0, 2] += (nw / 2) - center[0]
matrix[1, 2] += (nh / 2) - center[1]
rotate = cv2.warpAffine(image, matrix, (nw, nh))
rotate = cv2.resize(rotate, (w, h))
return rotate
class Perspective:
"""
Shear an image, for each pixel (x, y) in the sheared image, the new value is
taken from a position (x+factor_x*y, factor_y*x+y) in the origin image. Then
the sheared image will be rescaled to fit original size.
Args:
factor_x (Union[float, int]): Shear factor of horizontal direction.
Default: 0.
factor_y (Union[float, int]): Shear factor of vertical direction.
Default: 0.
"""
def __init__(self, ori_pos, dst_pos):
super(Perspective, self).__init__()
ori_pos = check_param_type('ori_pos', ori_pos, list)
dst_pos = check_param_type('dst_pos', dst_pos, list)
self.ori_pos = np.float32(ori_pos)
self.dst_pos = np.float32(dst_pos)
def __call__(self, image):
"""
Transform the image.
Args:
image(numpy.ndarray): Original image to be transformed.
Returns:
numpy.ndarray, transformed image.
"""
image = check_numpy_param('image', image)
h, w = image.shape[:2]
matrix = cv2.getPerspectiveTransform(self.ori_pos, self.dst_pos)
new_img = cv2.warpPerspective(image, matrix, (w, h))
return new_img
class MotionBlur:
"""
Motion blur for a given image.
Args:
degree (int): Degree of blur. This value must be positive. Default: 5.
angle: (union[float, int]): Direction of motion blur. Angle=0 means up and down motion blur. Angle is
counterclockwise.
Example:
>>> img = cv2.imread('1.png')
>>> img = np.array(img)
>>> angle = 0
>>> degree = 5
>>> trans = MotionBlur(degree=degree, angle=angle)
>>> new_img = trans(img)
"""
def __init__(self, degree=5, angle=45):
super(MotionBlur, self).__init__()
self.degree = check_int_positive('degree', degree)
self.degree = check_param_multi_types('degree', degree, [float, int])
self.angle = angle - 45
def __call__(self, image):
"""
Motion blur for a given image.
Args:
image (numpy.ndarray): Original image.
Returns:
numpy.ndarray, image after motion blur.
"""
image = check_numpy_param('image', image)
matrix = cv2.getRotationMatrix2D((self.degree / 2, self.degree / 2), self.angle, 1)
motion_blur_kernel = np.diag(np.ones(self.degree))
motion_blur_kernel = cv2.warpAffine(motion_blur_kernel, matrix, (self.degree, self.degree))
motion_blur_kernel = motion_blur_kernel / self.degree
blurred = cv2.filter2D(image, -1, motion_blur_kernel)
# convert to uint8
cv2.normalize(blurred, blurred, 0, 255, cv2.NORM_MINMAX)
blurred = np.array(blurred, dtype=np.uint8)
return blurred
class GradientBlur:
"""
Gradient blur.
Args:
point(union[tuple, list]): 2D coordinate of the Blur center point.
kernel_num(int): Number of blur kernels. Default: 5.
center(bool): Blurred or clear at the center of a specified point. Default: True.
Example:
>>> img = cv2.imread('xx.png')
>>> img = np.array(img)
>>> number = 5
>>> h, w = img.shape[:2]
>>> point = (int(h / 5), int(w / 5))
>>> center = True
>>> trans = GradientBlur(point, number, center)
>>> new_img = trans(img)
"""
def __init__(self, point, kernel_num=3, center=True):
super(GradientBlur).__init__()
point = check_param_multi_types('point', point, [list, tuple])
self.point = tuple(point)
self.kernel_num = check_int_positive('kernel_num', kernel_num)
self.center = check_param_type('center', center, bool)
def __call__(self, image):
"""
Args:
image(numpy.ndarray): Original image.
Returns:
numpy.ndarray, gradient blurred image.
"""
image = check_numpy_param('image', image)
w, h = image.shape[:2]
mask = np.zeros(image.shape, dtype=np.uint8)
masks = []
radius = max(w - self.point[0], self.point[0], h - self.point[1], self.point[1])
radius = int(radius / self.kernel_num)
for i in range(self.kernel_num):
circle = cv2.circle(mask.copy(), self.point, radius * (1 + i), (1, 1, 1), -1)
masks.append(circle)
blurs = []
for i in range(3, 3 + 2 * self.kernel_num, 2):
ksize = (i, i)
blur = cv2.GaussianBlur(image, ksize, 0)
blurs.append(blur)
dst = image.copy()
if self.center:
for i in range(self.kernel_num):
dst = masks[i] * dst + (1 - masks[i]) * blurs[i]
else:
for i in range(self.kernel_num - 1, -1, -1):
dst = masks[i] * blurs[self.kernel_num - 1 - i] + (1 - masks[i]) * dst
return dst
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):
point(union([tuple, list]): 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)))
height, width = img_src.shape[:2]
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.zeros_like(img_src, dtype=np.uint8)
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
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
return img_grad
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.
mode(str): mode must be in ['horizontal', 'vertical'].
"""
h, w = image.shape[:2]
if start_pos is None:
start_pos = 0.5
else:
if mode == 'horizontal':
start_pos = start_pos[0] / h
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))
mask = np.array(mask, dtype=np.uint8)
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])
mask = np.array(mask, dtype=np.uint8)
return mask
class GradientLuminance:
"""
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.3.
bright_rate(float): Control brightness of . A larger value indicates a larger gradient range. Default: 0.3.
pattern(str): Dark or light, this value must be in ['light', 'dark'].
mode(str): Gradient mode, value must be in ['circle', 'horizontal', 'vertical'].
Examples:
>>> 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
>>> trans = GradientLuminance(start, end, point, scope, pattern='light', mode='circle')
>>> img_new = trans(img)
"""
def __init__(self, color_start, color_end, start_point, scope=0.5, pattern='light', bright_rate=0.3, mode='circle'):
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)
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 __call__(self, image):
image = check_numpy_param('image', image)
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)
return img_new
class Perlin:
"""
Add perlin noise to given image.
Args:
ratio(float): Noise density. Default: 0.3.
shade(float): The degree of background shade color. Default: 0.1.
Examples:
>>> img = cv2.imread('xx.png')
>>> img = np.array(img)
>>> shade = 0.1
>>> ratio = 0.2
>>> trans = Perlin(ratio, shade)
>>> new_img = trans(img)
"""
def __init__(self, ratio, shade=0.1):
super(Perlin).__init__()
ratio = check_param_type('ratio', ratio, float)
ratio = check_param_in_range('ratio', ratio, 0, 1)
if ratio > 0.7:
self.ratio = 7
else:
self.ratio = int(ratio * 10)
shade = check_param_type('shade', shade, float)
self.shade = check_param_in_range('shade', shade, 0, 1)
def __call__(self, image):
"""
Args:
image(numpy.ndarray): Original image.
Returns:
numpy.ndarray, image with perlin noise.
"""
image = check_numpy_param('image', image)
noise = generate_fractal_noise_2d((1024, 1024), (2 ** self.ratio, 2 ** self.ratio), 4)
noise[noise < 0] = 0
noise[noise > 1] = 1
back = np.array((1 - noise) * 255, dtype=np.uint8)
back = cv2.resize(back, (image.shape[1], image.shape[0]))
back = np.resize(np.repeat(back, 3), image.shape)
dst = cv2.addWeighted(image, 1 - self.shade, back, self.shade, 0)
return dst
class BackShadow:
"""
Add back ground picture to given image.
Args:
template_path(str): Path of template pictures file.
shade(float): TWeight of background. Default: 0.1.
Examples:
>>> img = cv2.imread('xx.png')
>>> img = np.array(img)
>>> template_path = 'template/leaf'
>>> shade = 0.2
>>> trans = BackShadow(template_path, shade=shade)
>>> new_img = trans(img)
"""
def __init__(self, template_path, shade=0.1):
super(BackShadow).__init__()
if os.path.exists(template_path):
self.template_path = template_path
else:
msg = "Template_path is not exist"
LOGGER.error(TAG, msg)
raise ValueError(msg)
shade = check_param_type('shade', shade, float)
self.shade = check_param_in_range('shade', shade, 0, 1)
def __call__(self, image):
"""
Args:
image(numpy.ndarray): Original image.
Returns:
numpy.ndarray, image with background shadow.
"""
image = check_numpy_param('image', image)
file = os.listdir(self.template_path)
file_path = os.path.join(self.template_path, np.random.choice(file))
shadow = cv2.imread(file_path)
beta = 0
shadow = cv2.resize(shadow, (image.shape[1], image.shape[0]))
dst = cv2.addWeighted(image, 1 - self.shade, shadow, self.shade, beta)
return dst
class NaturalNoise:
"""
Add natural noise to an image.
Args:
ratio(float): Noise density. Default: 0.0002.
k_x_range(union[list, tuple]): Value range of the noise block length.
k_y_range(union[list, tuple]): Value range of the noise block width.
Examples:
>>> img = cv2.imread('xx.png')
>>> img = np.array(img)
>>> k_x_range=(1, 5)
>>> k_y_range=(3, 25)
>>> trans = NaturalNoise(range=0.0002)
>>> new_img = trans(img)
"""
def __init__(self, ratio=0.0002, k_x_range=(1, 5), k_y_range=(3, 25)):
super(NaturalNoise).__init__()
self.ratio = check_param_type('ratio', ratio, float)
k_x_range = check_param_multi_types('k_x_range', k_x_range, [list, tuple])
k_y_range = check_param_multi_types('k_y_range', k_y_range, [list, tuple])
self.k_x_range = tuple(k_x_range)
self.k_y_range = tuple(k_y_range)
def __call__(self, image):
"""
Add natural noise to given image.
Args:
image(numpy.ndarray): Original image.
Returns:
numpy.ndarray, image with natural noise.
"""
image = check_numpy_param('image', image)
randon_range = 100
w, h = image.shape[:2]
dst = np.ones((w, h, 3), dtype=np.uint8) * 255
for _ in range(5):
noise = np.ones((w, h, 3), dtype=np.uint8) * 255
rate = self.ratio / 5
mask = np.random.uniform(size=(w, h)) < rate
noise[mask] = np.random.randint(0, randon_range)
k_x, k_y = np.random.randint(*self.k_x_range), np.random.randint(*self.k_y_range)
kernel = np.ones((k_x, k_y), np.uint8)
erode = cv2.erode(noise, kernel, iterations=1)
dst = erode * (erode < randon_range) + dst * (1 - erode < randon_range)
# Add black point
for _ in range(np.random.randint(k_x * k_y / 2)):
x = np.random.randint(-k_x, k_x)
y = np.random.randint(-k_y, k_y)
matrix = np.array([[1, 0, y], [0, 1, x]], dtype=np.float)
affine = cv2.warpAffine(noise, matrix, (h, w))
dst = affine * (affine < randon_range) + dst * (1 - affine < randon_range)
# Add white point
for _ in range(int(k_x * k_y / 2)):
x = np.random.randint(-k_x / 2 - 1, k_x / 2 + 1)
y = np.random.randint(-k_y / 2 - 1, k_y / 2 + 1)
matrix = np.array([[1, 0, y], [0, 1, x]], dtype=np.float)
affine = cv2.warpAffine(noise, matrix, (h, w))
white = affine < randon_range
dst[white] = 255
mask = dst < randon_range
dst = image * (1 - mask) + dst * mask
dst = np.array(dst, dtype=np.uint8)
return dst
class Curve:
"""
Curve picture using sin method.
Args:
curves(union[float, int]): Divide width to curves of `2*math.pi`, which means how many curve cycles.
depth(union[float, int]): Amplitude of sin method.
mode(str): Direction of deformation. Optional value is 'vertical' or 'horizontal'.
Examples:
>>> img = cv2.imread('x.png')
>>> curves =1
>>> depth = 10
>>> trans = Curve(curves, depth, mode='vertical')
>>> img_new = trans(img)
"""
def __init__(self, curves=10, depth=10, mode='vertical'):
super(Curve).__init__()
self.curves = check_value_non_negative('curves', curves)
self.depth = check_value_non_negative('depth', depth)
if mode in ['vertical', 'horizontal']:
self.mode = mode
else:
msg = "Value of param mode must be in ['vertical', 'horizontal']"
LOGGER.error(TAG, msg)
raise ValueError(msg)
def __call__(self, image):
"""
Curve picture using sin method.
Args:
image(numpy.ndarray): Original image.
Returns:
numpy.ndarray, curved image.
"""
image = check_numpy_param('image', image)
if self.mode == 'vertical':
image = np.transpose(image, [1, 0, 2])
heights, widths = image.shape[:2]
src_x = np.zeros((heights, widths), np.float32)
src_y = np.zeros((heights, widths), np.float32)
for y in range(heights):
for x in range(widths):
src_x[y, x] = x
src_y[y, x] = y + self.depth * math.sin(x / (widths / self.curves / 2 / math.pi))
img_new = cv2.remap(image, src_x, src_y, cv2.INTER_LINEAR)
if self.mode == 'vertical':
img_new = np.transpose(img_new, [1, 0, 2])
return img_new
class BackgroundWord:
"""
Overlay the background image on the original image.
Args:
shade(float): Weight of background.
back(numpy.ndarray): Background Image. If none, mean background image is the as original image.
Examples:
>>> img = cv2.imread('x.png')
>>> back = cv2.imread('x.png')
>>> shade=0.2
>>> trans = BackgroundWord(shade, back)
>>> img_new = trans(img)
"""
def __init__(self, shade=0.1, back=None):
super(BackgroundWord).__init__()
self.shade = shade
self.back = back
def __call__(self, image):
image = check_numpy_param('image', image)
beta = 0
width, height = image.shape[:2]
x = np.random.randint(0, int(width / 5))
y = np.random.randint(0, int(height / 5))
matrix = np.array([[1, 0, y], [0, 1, x]], dtype=np.float)
affine = cv2.warpAffine(image.copy(), matrix, (height, width))
back = image.copy()
back[x:, y:] = affine[x:, y:]
dst = cv2.addWeighted(image, 1 - self.shade, back, self.shade, beta)
return dst

Loading…
Cancel
Save