Source code for augraphy.utilities.texturegenerator

import random

import cv2
import numba as nb
import numpy as np
from numba import config
from numba import jit
from sklearn.datasets import make_blobs

from augraphy.augmentations.lib import enhance_contrast


[docs] class TextureGenerator: """Core object to generate mask of texture in paper. 1. "normal" : Texture generated by multiple additions of normal distribution noise. 2. "strange" : Texture generated by an algorithm called "strange pattern". 3. "rough_stains" : A rough stains similar texture generated by using FFT. 4. "fine_stains" : A fine stains similar texture generated by using FFT. 5. "severe_stains" : A severe stains similar texture generated by using FFT. 6. “light_stains" : A light stains texture covering the whole image. Generated using additive Median filter. 7. "random_pattern" : A random pattern texture generated by using FFT. 8. "dot_granular" : A granular texture generated using single directional gradient on dots of filled circle. 9. "light_granular" : A light granular texture generated using single directional gradient on gaussian noise. 10. "rough_granular" : A rough granular texture generated using using single directional gradient and stacking largest dots to smallest dots. 11. "curvy_edge" : Texture with distinct curvy effect on the images edges. Generated by using FFT. 12. "broken_edge" : Texture with broken images edges effect. Generated by using FFT. :param numba_jit: The flag to enable numba jit to speed up the processing. :type numba_jit: int, optional """ def __init__(self, numba_jit=1): self.numba_jit = numba_jit config.DISABLE_JIT = bool(1 - numba_jit) # Adapted from this link: # # https://stackoverflow.com/questions/51646185/how-to-generate-a-paper-like-background-with-opencv
[docs] def generate_normal_texture(self, xsize, ysize, channel, value=255, sigma=1, turbulence=2): """Generate random texture through multiple iterations of normal distribution noise addition. :param xsize: The width of the generated noise. :type xsize: int :param ysize: The height of the generated noise. :type ysize: int :param channel: The number of channel in the generated noise. :type channel: int :param value: The initial value of the generated noise. :type value: int :param sigma: The bounds of noise fluctuations. :type sigma: float :param turbulence: The value to define how quickly big patterns will be replaced with the small ones. :type turbulence: int """ image_output = np.full((ysize, xsize), fill_value=value, dtype="float") ratio = min(xsize, ysize) while ratio != 1: new_ysize = int(ysize / ratio) new_xsize = int(xsize / ratio) result = np.random.normal(0, sigma, (new_xsize, new_ysize, channel)) if ratio != 1: result = cv2.resize(result, dsize=(xsize, ysize), interpolation=cv2.INTER_LINEAR) image_output += result ratio = (ratio // turbulence) or 1 image_output = np.clip(image_output, 0, 255) new_min = 32 new_max = 255 image_output = ((image_output - np.min(image_output)) / (np.max(image_output) - np.min(image_output))) * ( new_max - new_min ) + new_min # convert to uint8 image_output = np.uint8(image_output) # conver to color image if channel == 3: image_output = cv2.cvtColor(image_output, cv2.COLOR_GRAY2BGR) return image_output
# adapted from this repository: # https://github.com/NewYaroslav/strange_pattern
[docs] @staticmethod @jit(nopython=True, cache=True, parallel=True) def generate_strange_texture(oxsize, oysize): """Generate a random strange texture. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ background_value = random.uniform(0.04, 0.11) # initialize random parameter t_random = random.uniform(0, 100) m_random = [random.uniform(0, 100), random.uniform(0, 100)] # initialize output image_strange_texture = np.zeros((oysize, oxsize, 3)) # calculate color for y in nb.prange(oysize): for x in nb.prange(oxsize): # initial value value = int(x + t_random * 80.0 + m_random[0] * 10.0) ^ int(y + t_random * 80.0 + m_random[1] * 10.0) # update pixel value color = 1.0 if value <= 1: color = background_value if value % 2 == 0 and value > 2: color = background_value for i in range(3, int(np.floor(np.sqrt(float(value)))), random.randint(1, 10)): if value % i == 0: color = background_value # generate random color new_color = [ color / random.uniform(0.01, 3), color / random.uniform(0.01, 3), color / random.uniform(0.01, 3), ] # update color image_strange_texture[y, x] = new_color # rotate texture randomly image_strange_texture = np.rot90(image_strange_texture, random.randint(0, 3)) return image_strange_texture
[docs] def generate_broken_edge_texture(self, oxsize, oysize): """Generate a mask of broken edges based texture. :param oxsize: The width of the output mask. :type oxsize: int :param oysize: The height of the output mask. :type oysize: int """ iysize, ixsize = 400, 400 # mask of noise image_noise = np.full((iysize, ixsize), fill_value=255, dtype="uint8") # image center center_xs = [0, int(ixsize / 2), ixsize, int(ixsize / 2)] center_ys = [int(iysize / 2), 0, int(iysize / 2), iysize] noise_value = [128, 255] n_clusters = [300, 500] n_samples = [1080, 1280] stds = [int(ixsize / 4), int(iysize / 4)] n_samples = [ random.randint(n_samples[0], n_samples[1]) for i in range(random.randint(n_clusters[0], n_clusters[1])) ] std = random.randint(stds[0], stds[1]) generated_points_x = np.array([[0]], dtype=("float")) generated_points_y = np.array([[0]], dtype=("float")) for center_x, center_y in zip(center_xs, center_ys): # generate clusters of noises cgenerated_points_x, _ = make_blobs( n_samples=n_samples, center_box=(center_x, center_x), cluster_std=std, n_features=1, ) # generate clusters of noises cgenerated_points_y, _ = make_blobs( n_samples=n_samples, center_box=(center_y, center_y), cluster_std=std, n_features=1, ) generated_points_x = np.concatenate((generated_points_x, cgenerated_points_x), axis=0) generated_points_y = np.concatenate((generated_points_y, cgenerated_points_y), axis=0) # generate x and y points of noise generated_points_x = generated_points_x.astype("int") generated_points_y = generated_points_y.astype("int") # remove invalid points ind_delete_x1 = np.where(generated_points_x < 2) ind_delete_x2 = np.where(generated_points_x >= ixsize - 2) ind_delete_y1 = np.where(generated_points_y < 2) ind_delete_y2 = np.where(generated_points_y >= iysize - 2) ind_delete = np.concatenate( (ind_delete_x1, ind_delete_x2, ind_delete_y1, ind_delete_y2), axis=1, ) generated_points_x = np.delete(generated_points_x, ind_delete, axis=0) generated_points_y = np.delete(generated_points_y, ind_delete, axis=0) # update noise value image_random = np.random.random((iysize, ixsize)) image_random[image_random < noise_value[0] / 255] = 0 image_random[image_random > noise_value[1] / 255] = 0 image_random = (image_random * 255).astype("uint8") # update points with random value image_noise[generated_points_y, generated_points_x] = image_random[generated_points_y, generated_points_x] # apply blur image_noise = cv2.GaussianBlur(image_noise, (random.choice([7, 9, 11, 13]), random.choice([7, 9, 11, 13])), 0) # create edge texture image_edge_texture = image_noise + image_noise # resize to expected size image_edge_texture = cv2.resize(image_edge_texture, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return image_edge_texture
[docs] def generate_rough_stains_texture(self, oxsize, oysize): """Generate a rough stains similar texture using FFT. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 200, 200 wave_grid_output = self.generate_FFT_grid( xsize, ysize, f_iterations=(3, 5), g_iterations=(2, 4), rresolutions=(0.95, 0.95), rA=(5, 15), rf=(0.01, 0.03), srp=(0, 2 * np.pi), srkx=(-1, 1), srky=(-1, 1), crp=(0, 2 * np.pi), crkx=(-1, 1), crky=(-1, 1), ) # blur to smoothen texture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (3, 3), 0) # remove low frequency area wave_grid_output = self.remove_frequency(wave_grid_output, random.randint(25, 35)) # median filter to smoothen texture wave_grid_output = cv2.medianBlur(wave_grid_output, 5) # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_light_stains_texture(self, oxsize, oysize): """Generate light stains texture convering the whole image using additive Median filter. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 500, 500 number_granules = 800 granule_max_size = int(min(xsize, ysize) / 20) wave_grid_output = np.zeros((ysize, xsize), dtype="float") for _ in range(10): wave_grid = np.zeros((ysize, xsize), dtype="float") for _ in range(number_granules): y = np.random.randint(0, ysize) x = np.random.randint(0, xsize) intensity = np.random.randint(0, 25) # Intensity of the spot granule_size = random.randint(1, granule_max_size) wave_grid[y : y + granule_size, x : x + granule_size] += intensity wave_grid_output += cv2.medianBlur(np.uint8(wave_grid), 9).astype("float") wave_grid_output = np.uint8(wave_grid_output) wave_grid_output = cv2.GaussianBlur(wave_grid_output, (5, 5), 0) wave_grid_output = wave_grid_output[50:-50, 50:-50] # remove frequency > 10 frequency = 10 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # remove dark area wave_grid_output_brighten = (wave_grid_output.astype("float")) * 10 wave_grid_output_brighten[wave_grid_output_brighten > 255] = 255 wave_grid_output_brighten = np.uint8(wave_grid_output_brighten) # update dark area to brighten area min_brightness = 240 wave_grid_output[wave_grid_output < min_brightness] = wave_grid_output_brighten[ wave_grid_output < min_brightness ] # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_random_pattern_texture(self, oxsize, oysize): """Generate random pattern texture using FFT. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 500, 500 wave_grid_output = self.generate_FFT_grid( xsize, ysize, f_iterations=(1, 1), g_iterations=(1, 1), rresolutions=(0.95, 0.95), rA=(5, 15), rf=(0.01, 0.02), srp=(0, 2 * np.pi), srkx=(-1, 1), srky=(-1, 1), crp=(0, 2 * np.pi), crkx=(-1, 1), crky=(-1, 1), ) # blur to smoothen texture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (3, 3), 0) # remove frequency > 10 frequency = 10 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # remove border textures offset = 50 wave_grid_output = wave_grid_output[offset:-offset, offset:-offset] # rescale wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) wave_grid_output = 255 - np.uint8(wave_grid_output * 255) # remove frequency > 100 frequency = 100 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # rescale again wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) wave_grid_output = np.uint8(wave_grid_output * 255) # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_dot_granular_texture(self, oxsize, oysize): """Generate random granular texture using single directional gradient on dots of filled circle. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 1000, 1000 number_granules = 800 granule_min_size = np.floor(min(xsize, ysize) / 600) granule_max_size = np.ceil(min(xsize, ysize) / 495) wave_grid_output = np.zeros((ysize, xsize), dtype="float") for _ in range(30): intensity = np.random.randint(1, 2) # Intensity of the spot wave_grid = np.zeros((ysize, xsize), dtype="float") for _ in range(number_granules): y = np.random.randint(0, ysize) x = np.random.randint(0, xsize) granule_size = random.randint(granule_min_size, granule_max_size) cv2.circle(wave_grid, (x, y), granule_size, intensity, thickness=-1) wave_grid_output += wave_grid # stack images to create more dots effect wave_grid_output2 = np.fliplr(wave_grid_output) + np.flipud(wave_grid_output) wave_grid_output3 = np.fliplr(wave_grid_output2) + np.flipud(wave_grid_output2) wave_grid_output = np.fliplr(wave_grid_output3) + np.flipud(wave_grid_output3) # create single directional gradient effect offset = random.randint(1, 3) direction = random.choice([0, 1, 2, 3]) if direction == 0: wave_grid_output = wave_grid_output[offset:, :] - wave_grid_output[:-offset, :] elif direction == 1: wave_grid_output = wave_grid_output[:-offset, :] - wave_grid_output[offset:, :] elif direction == 2: wave_grid_output = wave_grid_output[:, offset:] - wave_grid_output[:, :-offset] else: wave_grid_output = wave_grid_output[:, :-offset] - wave_grid_output[:, offset:] # rescale into 0-1 wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) # convert to uint8 wave_grid_output = np.uint8(wave_grid_output * 255) wave_grid_output = cv2.GaussianBlur(wave_grid_output, (5, 5), 0) # remove dark area wave_grid_output_brighten = (wave_grid_output.astype("float")) * 1.75 wave_grid_output_brighten[wave_grid_output_brighten > 255] = 255 wave_grid_output_brighten = np.uint8(wave_grid_output_brighten) # update dark area to brighten area min_brightness = 230 wave_grid_output[wave_grid_output < min_brightness] = wave_grid_output_brighten[ wave_grid_output < min_brightness ] # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_light_granular_texture(self, oxsize, oysize): """Generate light granular texture using single directional gradient on gaussian noise. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 1000, 1000 # generate random gaussian noise wave_grid_output = np.zeros((ysize, xsize), dtype="uint8") kernel_size = 9 for i in range(10): wave_grid_output += cv2.GaussianBlur( np.random.randint(188, 255, size=(ysize, xsize), dtype="uint8"), (kernel_size, kernel_size), 0, ) wave_grid_output = cv2.GaussianBlur(wave_grid_output, (3, 3), 0) # remove frequency > 10 frequency = 10 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # create single directional gradient effect offset = random.randint(2, 4) direction = random.choice([0, 1, 2, 3]) if direction == 0: wave_grid_output = wave_grid_output[offset:, :] - wave_grid_output[:-offset, :] elif direction == 1: wave_grid_output = wave_grid_output[:-offset, :] - wave_grid_output[offset:, :] elif direction == 2: wave_grid_output = wave_grid_output[:, offset:] - wave_grid_output[:, :-offset] else: wave_grid_output = wave_grid_output[:, :-offset] - wave_grid_output[:, offset:] # rescale into 0-1 wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) # rescale to new range new_max = 255 new_min = random.randint(220, 250) wave_grid_output = wave_grid_output * (new_max - new_min) + new_min wave_grid_output = np.uint8(wave_grid_output) # smooth the texture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (9, 9), 0) # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_rough_granular_texture(self, oxsize, oysize): """Generate rough granular texture using single directional gradient and stacking largest dots to smallest dots. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ """Generate random granular texture using single directional gradient on dots of filled circle. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 1000, 1000 number_granules = 500 granule_min_size = np.floor(min(xsize, ysize) / 10) granule_max_size = np.ceil(min(xsize, ysize) / 5) wave_grid_output = np.zeros((ysize, xsize), dtype="float") for _ in range(50): intensity = np.random.randint(1, 2) # Intensity of the spot wave_grid = np.zeros((ysize, xsize), dtype="float") for _ in range(number_granules): y = np.random.randint(0, ysize) x = np.random.randint(0, xsize) granule_size = random.randint(granule_min_size, granule_max_size) cv2.circle(wave_grid, (x, y), granule_size, intensity, thickness=-1) wave_grid_output += wave_grid # reduce granule size granule_min_size = np.floor(granule_min_size * 0.9) granule_max_size = np.ceil(granule_max_size * 0.9) # stack images to create more dots effect wave_grid_output2 = np.fliplr(wave_grid_output) + np.flipud(wave_grid_output) wave_grid_output3 = np.fliplr(wave_grid_output2) + np.flipud(wave_grid_output2) wave_grid_output = np.fliplr(wave_grid_output3) + np.flipud(wave_grid_output3) # create single directional gradient effect direction = random.choice([0, 1, 2, 3]) offset = random.randint(1, 3) if direction == 0: wave_grid_output = wave_grid_output[offset:, :] - wave_grid_output[:-offset, :] elif direction == 1: wave_grid_output = wave_grid_output[:-offset, :] - wave_grid_output[offset:, :] elif direction == 2: wave_grid_output = wave_grid_output[:, offset:] - wave_grid_output[:, :-offset] else: wave_grid_output = wave_grid_output[:, :-offset] - wave_grid_output[:, offset:] # rescale into 0-1 wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) # convert to uint8 wave_grid_output = np.uint8(wave_grid_output * 255) wave_grid_output = cv2.GaussianBlur(wave_grid_output, (5, 5), 0) # remove dark area wave_grid_output_brighten = (wave_grid_output.astype("float")) * 1.75 wave_grid_output_brighten[wave_grid_output_brighten > 255] = 255 wave_grid_output_brighten = np.uint8(wave_grid_output_brighten) # update dark area to brighten area min_brightness = 230 wave_grid_output[wave_grid_output < min_brightness] = wave_grid_output_brighten[ wave_grid_output < min_brightness ] # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_curvy_edge_texture(self, oxsize, oysize): """Generate a masked of curves based edge texture using FFT. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 500, 500 wave_grid_output = self.generate_FFT_grid( xsize, ysize, f_iterations=(1, 1), g_iterations=(2, 4), rresolutions=(0.95, 0.95), rA=(5, 15), rf=(0.05, 0.1), srp=(0, 2 * np.pi), srkx=(-1, 1), srky=(-1, 1), crp=(0, 2 * np.pi), crkx=(-1, 1), crky=(-1, 1), ) # blur to smoothen texture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (3, 3), 0) # remove frequency > 100 frequency = 100 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # rescale wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) wave_grid_output = np.uint8(wave_grid_output * 255) # blur to smoothen trexture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (9, 9), 0) # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_fine_stains_texture(self, oxsize, oysize): """Generate a fine stains similar texture using combination of normal distribution noise and FFT. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 500, 500 wave_grid_output = self.generate_FFT_grid( xsize, ysize, f_iterations=(1, 1), g_iterations=(3, 5), rresolutions=(0.95, 0.95), rA=(5, 15), rf=(0.01, 0.02), srp=(0, 2 * np.pi), srkx=(-1, 1), srky=(-1, 1), crp=(0, 2 * np.pi), crkx=(-1, 1), crky=(-1, 1), ) wave_grid_output += self.generate_normal_texture(ysize, xsize, 1, value=255, sigma=1, turbulence=2) # blur to smoothen texture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (3, 3), 0) # remove frequency > 10 frequency = 10 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # remove border textures offset = 50 wave_grid_output = wave_grid_output[offset:-offset, offset:-offset] # rescale wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) wave_grid_output = 255 - np.uint8(wave_grid_output * 255) # remove frequency > 100 frequency = 100 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # rescale again wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) wave_grid_output = np.uint8(wave_grid_output * 255) # blur to smoothen texture wave_grid_output = cv2.GaussianBlur(wave_grid_output, (3, 3), 0) # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_severe_stains_texture(self, oxsize, oysize): """Generate severe stains texture using combination of fine and rough stains texture. :param oxsize: The width of the output texture image. :type oxsize: int :param oysize: The height of the output texture image. :type oysize: int """ # fixed internal resolution ysize, xsize = 500, 500 # merge fine and rough stains wave_grid_output1 = self.generate_fine_stains_texture(xsize, ysize) wave_grid_output2 = self.generate_rough_stains_texture(xsize, ysize) wave_grid_output = wave_grid_output1 * wave_grid_output2 # remove frequency > 50 frequency = 50 wave_grid_output = self.remove_frequency(wave_grid_output, frequency) # rescale wave_grid_output = (wave_grid_output - wave_grid_output.min()) / ( wave_grid_output.max() - wave_grid_output.min() ) # convert to uint8 wave_grid_output = np.uint8(wave_grid_output * 255) # median filter to smoothen texture wave_grid_output = cv2.medianBlur(wave_grid_output, 3) # resize to output size wave_grid_output = cv2.resize(wave_grid_output, (oxsize, oysize), interpolation=cv2.INTER_LINEAR) return wave_grid_output
[docs] def generate_FFT_grid( self, xsize, ysize, f_iterations, g_iterations, rresolutions, rA, rf, srp, srkx, srky, crp, crkx, crky, ): """Generate random wave grid, process it in FFT and convert it back into spatial domain. :param xsize: The width of the output image. :type xsize: int :param ysize: The height of the output image. :type ysize: int :param f_iterations: Tuple of ints in determining the number of iterations in adding FFT converted wave grid. :type f_iterations: tuple :param g_iterations: Tuple of ints in determining the number of iterations in summing grid waves. :type g_iterations: tuple :param rresolutions: Tuple of floats in determing the resolution of grid image. :type rresolutions: tuple :param rA: Tuple of ints in determining the amplitude of waves. :type rA: tuple :param rf: Tuple of floats in determining the frequency of waves. :type rf tuple :param srp: Tuple of floats in determining the phase of sine waves. :type srp tuple :param srkx: Tuple of floats in determining the x-component of sine wave vector. :type srkx tuple :param srky: Tuple of floats in determining the y-component of sine wave vector. :type srky tuple :param crp: Tuple of floats in determining the phase of cosine waves. :type crp tuple :param crkx: Tuple of floats in determining the x-component of cosine wave vector. :type crkx tuple :param crky: Tuple of floats in determining the y-component of cosine wave vector. :type crky tuple """ wave_grid_output = np.zeros((ysize, xsize), dtype="uint8") for i in range(random.randint(f_iterations[0], f_iterations[1])): # fixed resolution of the wave image resolution = random.uniform(rresolutions[0], rresolutions[1]) # Create a 2D grid of coordinates x_array = np.arange(-xsize / 2, xsize / 2) * resolution y_array = np.arange(-ysize / 2, ysize / 2) * resolution x_grid, y_grid = np.meshgrid(x_array, y_array) wave_grid_fft_shifted = np.zeros((ysize, xsize), dtype="complex") for i in range(random.randint(g_iterations[0], g_iterations[1])): # iterations for adding waves wave_grid = self.generate_wave_grid( x_grid, y_grid, xsize, ysize, iterations=g_iterations, rA=rA, rf=rf, srp=srp, srkx=srkx, srky=srky, crp=crp, crkx=crkx, crky=crky, ) # Compute the FFT of the wave heights, shift the zero-frequency component to the center and then sum them wave_grid_fft_shifted += np.fft.fftshift(np.fft.fft2(wave_grid)) # unshift the FFT component new_wave_grid = np.fft.ifft2(np.fft.ifftshift((wave_grid_fft_shifted))) # get the real part only new_wave_grid = np.real(new_wave_grid) # scale to 0 -1 new_wave_grid = (new_wave_grid - new_wave_grid.min()) / (new_wave_grid.max() - new_wave_grid.min()) # convert to uint8 new_wave_grid = np.uint8(new_wave_grid * 255) # merge into output wave_grid_output += new_wave_grid return wave_grid_output
[docs] def generate_wave_grid(self, xgrid, ygrid, xsize, ysize, iterations, rA, rf, srp, srkx, srky, crp, crkx, crky): """Create grid of waves using heights of sine and cosine waves. :param xgrid: The x coordinates grid. :type xgrid: numpy array :param ygrid: The y coordinates grid. :type ygrid: numpy array :param xsize: The width of the output grid image. :type xsize: int :param ysize: The height of the output grid image. :type ysize: int :param iterations: The number of iterations in summing waves. :type iterations: tuple :param rA: Tuple of ints in determining the amplitude of waves. :type rA: tuple :param rf: Tuple of floats in determining the frequency of waves. :type rf tuple :param srp: Tuple of floats in determining the phase of sine waves. :type srp tuple :param srkx: Tuple of floats in determining the x-component of sine wave vector. :type srkx tuple :param srky: Tuple of floats in determining the y-component of sine wave vector. :type srky tuple :param crp: Tuple of floats in determining the phase of cosine waves. :type crp tuple :param crkx: Tuple of floats in determining the x-component of cosine wave vector. :type crkx tuple :param crky: Tuple of floats in determining the y-component of cosine wave vector. :type crky tuple """ # iterations for adding waves current_iterations = random.randint(iterations[0], iterations[1]) wave_grid = np.zeros((ysize, xsize), dtype="float") for i in range(current_iterations): # Calculate the wave height using a sine function A = np.random.uniform(rA[0], rA[1]) # Amplitude f = np.random.uniform(rf[0], rf[1]) # Frequency p = np.random.uniform(srp[0], srp[1]) # Phase kx = np.random.uniform(srkx[0], srkx[1]) # x-component of wave vector ky = np.random.uniform(srky[0], srky[1]) # y-component of wave vector h_sine = A * np.sin(2 * np.pi * (f * (kx * xgrid + ky * ygrid) - p)) # Calculate the wave height using a cosine function A = np.random.uniform(rA[0], rA[1]) # Amplitude f = np.random.uniform(rf[0], rf[1]) # Frequency p = np.random.uniform(crp[0], crp[1]) # Phase kx = np.random.uniform(crkx[0], crkx[1]) # x-component of wave vector ky = np.random.uniform(crky[0], crky[1]) # y-component of wave vector h_cosine = A * np.cos(2 * np.pi * (f * (kx * xgrid + ky * ygrid) - p)) # combine heights from sine and cosine wave_grid = h_sine + h_cosine return wave_grid
[docs] def remove_frequency(self, wave_grid_output, frequency1, frequency2=None): """Remove image area bigger than the input frequency by using FFT. :param wave_grid_output: The input image. :type wave_grid_output: numpy array :param frequency1: The left end of frequency threshold. :type frequency1: int :param frequency2: The right end of frequency threshold. :type frequency2: int, optional """ ysize, xsize = wave_grid_output.shape[:2] cy, cx = ysize // 2, xsize // 2 mask = np.ones((ysize, xsize), np.uint8) r = frequency1 # compute mask to remove low frequency area y, x = np.ogrid[:ysize, :xsize] mask_area = (x - cx) ** 2 + (y - cy) ** 2 <= r * r mask[mask_area] = 0 if frequency2 is not None: r = frequency2 mask_area = (x - cx) ** 2 + (y - cy) ** 2 > r * r mask[mask_area] = 0 # convert to fft and shift to zero-frequency wave_grid_output_fft = np.fft.fft2(wave_grid_output) wave_grid_output_fft_shifted = np.fft.fftshift(wave_grid_output_fft) # apply mask and inverse DFT wave_grid_output_fft_shifted *= mask wave_grid_output2_fft = np.fft.ifft2(np.fft.ifftshift(wave_grid_output_fft_shifted)) wave_grid_output2 = np.abs(wave_grid_output2_fft) # normalize image back to 0 - 255 wave_grid_output2 = (wave_grid_output2 - wave_grid_output2.min()) / ( wave_grid_output2.max() - wave_grid_output2.min() ) wave_grid_output = 255 - np.uint8(wave_grid_output2 * 255) return wave_grid_output
[docs] def quilt_texture(self, image_texture, patch_size, patch_number_width, patch_number_height): """Generate new texture image by quilting patches of input image. :param image_texture: The input image texture. :type image_texture: numpy array :param patch_size: The size of each image patch. :type patch_size: int :param patch_number_width: The number of image patch in horizontal direction. :type patch_number_width: int :param patch_number_height: The number of image patch in vertical direction. :type patch_number_height: int """ overlap = patch_size // 5 # size of output ysize = (patch_number_height * patch_size) - (patch_number_height - 1) * overlap xsize = (patch_number_width * patch_size) - (patch_number_width - 1) * overlap # convert from gray to bgr is_gray = 0 if len(image_texture.shape) < 3: is_gray = 1 image_texture = cv2.cvtColor(image_texture, cv2.COLOR_GRAY2BGR) # output image_quilt = np.zeros((ysize, xsize, image_texture.shape[2]), dtype="uint8") # size of image texture ysize, xsize = image_texture.shape[:2] # hsv channel of texture image_hsv = cv2.cvtColor(image_texture, cv2.COLOR_BGR2HSV) # get a reference patch's hue, saturation and value n = 0 while n < 10: y = np.random.randint(ysize - patch_size) x = np.random.randint(xsize - patch_size) # to prevent black or white blank image if ( np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 2]) < 245 and np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 2]) > 10 ): break n += 1 h_reference = np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 0]) s_reference = np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 1]) v_reference = np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 2]) offset = 10 h_range = [h_reference - offset, h_reference + offset] s_range = [s_reference - offset, s_reference + offset] v_range = [v_reference - offset, v_reference + offset] # generate and apply random patch for i in range(patch_number_height): for j in range(patch_number_width): y = i * (patch_size - overlap) x = j * (patch_size - overlap) image_patch = self.get_random_patch( image_texture, image_hsv, patch_size, ysize, xsize, h_range, s_range, v_range, ) image_quilt[y : y + patch_size, x : x + patch_size] = image_patch # smoothing image_quilt = cv2.medianBlur(image_quilt, ksize=11) # enhance contrast of texture image_quilt = enhance_contrast(image_quilt) # image follows input texture color channel if is_gray: image_quilt = cv2.cvtColor(image_quilt, cv2.COLOR_BGR2GRAY) return image_quilt
[docs] def get_random_patch(self, image_texture, image_hsv, patch_size, ysize, xsize, h_range, s_range, v_range): """Get patch of image from texture based on input hue, saturation and value range. :param image_texture: The input image texture. :type image_texture: numpy array :param image_hsv: The input image texture in HSV channel. :type image_hsv: numpy array :param patch_size: The size of each image patch. :type patch_size: int :param y_size: The height of image texture. :type y_size: int :param x_size: The width of image texture. :type x_size: int :param h_range: The range of reference hue values. :type h_range: tuple :param s_range: The range of reference saturation values. :type s_range: tuple :param v_range: The range of reference value values. :type v_range: tuple """ n = 0 y = np.random.randint(ysize - patch_size) x = np.random.randint(xsize - patch_size) image_patch = image_texture[y : y + patch_size, x : x + patch_size] # use a fixed number to prevent infinity loops while n < 10: y = np.random.randint(ysize - patch_size) x = np.random.randint(xsize - patch_size) # get mean of h, s and v channel of current patch h_mean = np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 0]) s_mean = np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 1]) v_mean = np.mean(image_hsv[y : y + patch_size, x : x + patch_size, 2]) if ( h_mean >= h_range[0] and h_mean < h_range[1] and s_mean >= s_range[0] and s_mean < s_range[1] and v_mean >= v_range[0] and v_mean < v_range[1] ): # get patch of image image_patch = image_texture[y : y + patch_size, x : x + patch_size] # apply gamma correction mid = np.mean(v_range) / 255 gamma = np.log(mid * 255) / np.log(v_mean) image_patch = np.power(image_patch, gamma).clip(0, 255).astype(np.uint8) break n += 1 return image_patch
def __call__( self, texture_type="random", texture_width=1000, texture_height=1000, quilt_texture="random", quilt_size=(25, 40), ): """Main function to generate random textures. :param texture_type: Types of image texture. :type texture_type: string (optional) :param texture_width: Width of image texture output. :type texture_width: int (optional) :param texture_height: height of image texture output. :type texture_height: int (optional) :param quilt_texture: Flag to enable or disable the quilting of generated texture. :type quilt_texture: int or string (optional) :param quilt_size: Tuple of ints in determining the size of texture patch in quilting process. :type quilt_size: tuple (optional) """ # check for image texture generation if texture_type == "random": texture_type = random.choice( [ "normal", "strange", "rough_stains", "fine_stains", "severe_stains", "light_stains", "random_pattern", "dot_granular", "light_granular", "rough_granular", "curvy_edge", "broken_edge", ], ) if texture_type == "normal": image_texture = self.generate_normal_texture( texture_width, texture_height, 1, sigma=random.randint(3, 5), turbulence=random.randint(3, 9), ) elif texture_type == "strange": image_texture = self.generate_strange_texture(texture_width, texture_height) image_texture = cv2.cvtColor(np.uint8(image_texture * 255), cv2.COLOR_BGR2GRAY) image_texture = cv2.resize(image_texture, (texture_width, texture_height), interpolation=cv2.INTER_LINEAR) elif texture_type == "rough_stains": image_texture = self.generate_rough_stains_texture(texture_width, texture_height) elif texture_type == "fine_stains": image_texture = self.generate_fine_stains_texture(texture_width, texture_height) elif texture_type == "severe_stains": image_texture = self.generate_severe_stains_texture(texture_width, texture_height) elif texture_type == "light_stains": image_texture = self.generate_light_stains_texture(texture_width, texture_height) elif texture_type == "random_pattern": image_texture = self.generate_random_pattern_texture(texture_width, texture_height) elif texture_type == "dot_granular": image_texture = self.generate_dot_granular_texture(texture_width, texture_height) elif texture_type == "light_granular": image_texture = self.generate_light_granular_texture(texture_width, texture_height) elif texture_type == "rough_granular": image_texture = self.generate_rough_granular_texture(texture_width, texture_height) elif texture_type == "curvy_edge": image_texture = self.generate_curvy_edge_texture(texture_width, texture_height) else: image_texture = self.generate_broken_edge_texture(texture_width, texture_height) # get mask of edge texture _, image_texture_binary = cv2.threshold(image_texture, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # compute contours contours, hierarchy = cv2.findContours( image_texture_binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE, ) # initialize mask of edge texture image_texture_mask = np.zeros_like(image_texture, dtype="uint8") # find largest inner contour as edge texture for contour in contours: x0, y0, cwidth, cheight = cv2.boundingRect(contour) area = cv2.contourArea(contour) if ( area > (texture_height * texture_width * 0.6) and cwidth != texture_width and cheight != texture_height ): image_texture_mask = cv2.drawContours(image_texture_mask, [contour], -1, (255), cv2.FILLED) break # remove area outside edge texture image_texture[image_texture_mask <= 0] = 0 # check for image quilting if quilt_texture == "random": quilt_texture = random.choice([0, 1]) if quilt_texture: patch_size = random.randint(quilt_size[0], quilt_size[1]) patch_number_width = int(texture_width / patch_size) patch_number_height = int(texture_height / patch_size) image_texture = self.quilt_texture(image_texture, patch_size, patch_number_width, patch_number_height) # resize to output size image_texture = cv2.resize(image_texture, (texture_width, texture_height), interpolation=cv2.INTER_LINEAR) return image_texture