Source code for genalog.degradation.effect

# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# ---------------------------------------------------------

from math import floor

import cv2
import numpy as np


[docs]def blur(src, radius=5): """Wrapper function for cv2.GaussianBlur Arguments: src (numpy.ndarray) : source image of shape (rows, cols) radius (int, optional) : size of the square kernel, MUST be an odd integer. Defaults to 5. Returns: numpy.ndarray: a copy of the source image after apply the effect """ return cv2.GaussianBlur(src, (radius, radius), cv2.BORDER_DEFAULT)
[docs]def overlay_weighted(src, background, alpha, beta, gamma=0): """overlay two images together, pixels from each image is weighted as follow dst[i] = alpha*src[i] + beta*background[i] + gamma Arguments: src (numpy.ndarray) : source image of shape (rows, cols) background (numpy.ndarray) : background image. Must be in same shape are `src` alpha (float) : transparent factor for the foreground beta (float) : transparent factor for the background gamma (int, optional) : luminance constant. Defaults to 0. Returns: numpy.ndarray: a copy of the source image after apply the effect """ return cv2.addWeighted(src, alpha, background, beta, gamma).astype(np.uint8)
[docs]def overlay(src, background): """Overlay two images together via bitwise-and: dst[i] = src[i] & background[i] Arguments: src (numpy.ndarray) : source image of shape (rows, cols) background (numpy.ndarray) : background image. Must be in same shape are `src` Returns: numpy.ndarray: a copy of the source image after apply the effect """ return cv2.bitwise_and(src, background).astype(np.uint8)
[docs]def translation(src, offset_x, offset_y): """Shift the image in x, y direction Arguments: src (numpy.ndarray) : source image of shape (rows, cols) offset_x (int) : pixels in the x direction. Positive value shifts right and negative shifts right. offset_y (int) : pixels in the y direction. Positive value shifts down and negative shifts up. Returns: numpy.ndarray: a copy of the source image after apply the effect """ rows, cols = src.shape trans_matrix = np.float32([[1, 0, offset_x], [0, 1, offset_y]]) # size of the output image should be in the form of (width, height) dst = cv2.warpAffine(src, trans_matrix, (cols, rows), borderValue=255) return dst.astype(np.uint8)
[docs]def bleed_through(src, background=None, alpha=0.8, gamma=0, offset_x=0, offset_y=5): """Apply bleed through effect, background is flipped horizontally. Arguments: src (numpy.ndarray) : source image of shape (rows, cols) background (numpy.ndarray, optional) : background image. Must be in same shape as foreground. Defaults to None. alpha (float, optional) : transparent factor for the foreground. Defaults to 0.8. gamma (int, optional) : luminance constant. Defaults to 0. offset_x (int, optional) : background translation offset. Defaults to 0. Positive value shifts right and negative shifts right. offset_y (int, optional) : background translation offset. Defaults to 5. Positive value shifts down and negative shifts up. Returns: numpy.ndarray: a copy of the source image after apply the effect. Pixel value ranges [0, 255] """ if background is None: background = src.copy() background = cv2.flip(background, 1) # flipped horizontally background = translation(background, offset_x, offset_y) beta = 1 - alpha return overlay_weighted(src, background, alpha, beta, gamma)
[docs]def pepper(src, amount=0.05): """Randomly sprinkle dark pixels on src image. Wrapper function for skimage.util.noise.random_noise(). See https://scikit-image.org/docs/stable/api/skimage.util.html#random-noise Arguments: src (numpy.ndarray) : source image of shape (rows, cols) amount (float, optional) : proportion of pixels in range [0, 1] to apply the effect. Defaults to 0.05. Returns: numpy.ndarray: a copy of the source image after apply the effect. Pixel value ranges [0, 255] as uint8. """ dst = src.copy() # Method returns random floats in uniform distribution [0, 1) noise = np.random.random(src.shape) dst[noise < amount] = 0 return dst.astype(np.uint8)
[docs]def salt(src, amount=0.3): """Randomly sprinkle white pixels on src image. Wrapper function for skimage.util.noise.random_noise(). See https://scikit-image.org/docs/stable/api/skimage.util.html#random-noise Arguments: src (numpy.ndarray) : source image of shape (rows, cols) amount (float, optional) : proportion of pixels in range [0, 1] to apply the effect. Defaults to 0.05. Returns: numpy.ndarray: a copy of the source image after apply the effect. Pixel value ranges [0, 255] """ dst = src.copy() # Method returns random floats in uniform distribution [0, 1) noise = np.random.random(src.shape) dst[noise < amount] = 255 return dst.astype(np.uint8)
[docs]def salt_then_pepper(src, salt_amount=0.1, pepper_amount=0.05): """Randomly add salt then add pepper onto the image. Arguments: src (numpy.ndarray) : source image of shape (rows, cols) salt_amount (float) : proportion of pixels in range [0, 1] to apply the salt effect. Defaults to 0.1. pepper_amount (float) : proportion of pixels in range [0, 1] to apply the pepper effect. Defaults to 0.05. Returns: numpy.ndarray: a copy of the source image after apply the effect. Pixel value ranges [0, 255] as uint8. """ salted = salt(src, amount=salt_amount) return pepper(salted, amount=pepper_amount)
[docs]def pepper_then_salt(src, pepper_amount=0.05, salt_amount=0.1): """Randomly add pepper then salt onto the image. Arguments: src (numpy.ndarray) : source image of shape (rows, cols) pepper_amount (float) : proportion of pixels in range [0, 1] to apply the pepper effect. Defaults to 0.05. salt_amount (float) : proportion of pixels in range [0, 1] to apply the salt effect. Defaults to 0.1. Returns: numpy.ndarray: a copy of the source image after apply the effect. Pixel value ranges [0, 255] as uint8. """ peppered = pepper(src, amount=pepper_amount) return salt(peppered, amount=salt_amount)
[docs]def create_2D_kernel(kernel_shape, kernel_type="ones"): """Create 2D kernel for morphological operations. Arguments: kernel_shape (tuple) : shape of the kernel (rows, cols) kernel_type (str, optional) : type of kernel. Defaults to "ones". :: All supported kernel types are below: "ones": kernel is filled with all 1s in shape (rows, cols) [[1,1,1], [1,1,1], [1,1,1]] "upper_triangle": upper triangular matrix filled with ones [[1,1,1], [0,1,1], [0,0,1]] "lower_triangle": lower triangular matrix filled with ones [[1,0,0], [1,1,0], [1,1,1]] "x": "X" shape cross [[1,0,1], [0,1,0], [1,0,1]] "plus": "+" shape cross [[0,1,0], [1,1,1], [0,1,0]] "ellipse": elliptical kernel [[0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0]] Raises: ValueError: if kernel is not a 2-element tuple or kernel_type is not one of the supported values Returns: numpy.ndarray: a 2D array of shape `kernel_shape`. """ if len(kernel_shape) != 2: raise ValueError("Kernel shape must be a tuple of 2 integers") kernel_rows, kernel_cols = kernel_shape if kernel_type == "ones": kernel = np.ones(kernel_shape) elif kernel_type == "upper_triangle": kernel = np.triu(np.ones(kernel_shape)) elif kernel_type == "lower_triangle": kernel = np.tril(np.ones(kernel_shape)) elif kernel_type == "x": diagonal = np.eye(kernel_rows, kernel_cols) kernel = np.add(diagonal, np.fliplr(diagonal)) kernel[kernel > 1] = 1 elif kernel_type == "plus": kernel = np.zeros(kernel_shape) center_col = floor(kernel.shape[0] / 2) center_row = floor(kernel.shape[1] / 2) kernel[:, center_col] = 1 kernel[center_row, :] = 1 elif kernel_type == "ellipse": kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, kernel_shape) else: valid_kernel_types = { "ones", "upper_triangle", "lower_triangle", "x", "plus", "ellipse", } raise ValueError( f"Invalid kernel_type: {kernel_type}. Valid types are {valid_kernel_types}" ) return kernel.astype(np.uint8)
[docs]def morphology(src, operation="open", kernel_shape=(3, 3), kernel_type="ones"): """Dynamic calls different morphological operations ("open", "close", "dilate" and "erode") with the given parameters Arguments: src (numpy.ndarray) : source image of shape (rows, cols) operation (str, optional) : name of a morphological operation: ``("open", "close", "dilate", "erode")`` Defaults to ``"open"``. kernel_shape (tuple, optional) : shape of the kernel (rows, cols). Defaults to (3,3). kernel_type (str, optional) : type of kernel. ``("ones", "upper_triangle", "lower_triangle", "x", "plus", "ellipse")`` Defaults to ``"ones"``. Returns: numpy.ndarray: a copy of the source image after apply the effect. """ kernel = create_2D_kernel(kernel_shape, kernel_type) if operation == "open": return open(src, kernel) elif operation == "close": return close(src, kernel) elif operation == "dilate": return dilate(src, kernel) elif operation == "erode": return erode(src, kernel) else: valid_operations = ["open", "close", "dilate", "erode"] raise ValueError( f"Invalid morphology operation '{operation}'. Valid morphological operations are {valid_operations}" )
[docs]def open(src, kernel): """ "open" morphological operation. Like morphological "erosion", it removes foreground pixels (white pixels), however it is less destructive than erosion. For more information see: 1. https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html 2. http://homepages.inf.ed.ac.uk/rbf/HIPR2/open.htm Arguments: src (numpy.ndarray) : source image of shape (rows, cols) kernel (numpy.ndarray) : a 2D array for structuring the morphological effect Returns: numpy.ndarray: a copy of the source image after apply the effect. """ return cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel)
[docs]def close(src, kernel): """ "close" morphological operation. Like morphological "dilation", it grows the boundary of the foreground (white pixels), however, it is less destructive than dilation of the original boundary shape. For more information see: 1. https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html 2. http://homepages.inf.ed.ac.uk/rbf/HIPR2/close.htm Arguments: src (numpy.ndarray) : source image of shape (rows, cols) kernel (numpy.ndarray) : a 2D array for structuring the morphological effect Returns: numpy.ndarray: a copy of the source image after apply the effect. """ return cv2.morphologyEx(src, cv2.MORPH_CLOSE, kernel)
[docs]def erode(src, kernel): """ "erode" morphological operation. Erodes foreground pixels (white pixels). For more information see: 1. https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html 2. http://homepages.inf.ed.ac.uk/rbf/HIPR2/erode.htm Arguments: src (numpy.ndarray) : source image of shape (rows, cols) kernel (numpy.ndarray) : a 2D array for structuring the morphological effect Returns: numpy.ndarray: a copy of the source image after apply the effect. """ return cv2.erode(src, kernel)
[docs]def dilate(src, kernel): """ "dilate" morphological operation. Grows foreground pixels (white pixels). For more information see: 1. https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html 2. http://homepages.inf.ed.ac.uk/rbf/HIPR2/dilate.htm Arguments: src (numpy.ndarray) : source image of shape (rows, cols) kernel (numpy.ndarray) : a 2D array for structuring the morphological effect Returns: numpy.ndarray: a copy of the source image after apply the effect. """ return cv2.dilate(src, kernel)