import os
import random
from glob import glob
import cv2
import numpy as np
from augraphy.augmentations.lib import add_noise
from augraphy.augmentations.lib import generate_average_intensity
from augraphy.augmentations.lib import load_image_from_cache
from augraphy.base.augmentation import Augmentation
from augraphy.utilities.overlaybuilder import OverlayBuilder
[docs]
class BleedThrough(Augmentation):
"""Emulates bleed through effect from the combination of ink bleed and
gaussian blur operations.
:param intensity_range: Pair of floats determining the range from which
noise intensity is sampled.
:type intensity: tuple, optional
:param color_range: Pair of ints determining the range from which color
noise is sampled.
:type color_range: tuple, optional
:param ksize: Tuple of height/width pairs from which to sample the kernel
size. Higher value increases the spreadness of bleeding effect.
:type ksizes: tuple, optional
:param sigmaX: Standard deviation of the kernel along the x-axis.
:type sigmaX: float, optional
:param alpha: Intensity of bleeding effect, recommended value range from
0.1 to 0.5.
:type alpha: float, optional
:param offsets: Tuple of x and y offset pair to shift the bleed through
effect from original input.
:type offsets: tuple, optional
:param p: The probability this Augmentation will be applied.
:type p: float, optional
"""
def __init__(
self,
intensity_range=(0.1, 0.9),
color_range=(0, 224),
ksize=(17, 17),
sigmaX=1,
alpha=0.2,
offsets=(20, 20),
p=1,
):
super().__init__(p=p)
self.intensity_range = intensity_range
self.color_range = color_range
self.ksize = ksize
self.sigmaX = sigmaX
self.alpha = alpha
self.offsets = offsets
# Constructs a string representation of this Augmentation.
def __repr__(self):
return f"BleedThrough(intensity_range={self.intensity_range}, color_range={self.color_range}, ksize={self.ksize}, sigmaX={self.sigmaX},alpha={self.alpha},offsets={self.offsets},p={self.p})"
[docs]
def blend(self, img, img_bleed, alpha):
"""Blend two images based on the alpha value to create bleedthrough effect.
:param img: The background image to apply the blending function.
:type img: numpy.array (numpy.uint8)
:param img_bleed: The foreground image to apply the blending function.
:type img_bleed: numpy.array (numpy.uint8)
:param alpha: The alpha value of foreground image for the blending function.
:type alpha: float
"""
# convert to single channel to avoud unnecessary noise in colour image
if len(img_bleed.shape) > 2:
img_bleed_input = cv2.cvtColor(
img_bleed.astype("uint8"),
cv2.COLOR_BGR2GRAY,
)
else:
img_bleed_input = img_bleed.astype("uint8")
# if the bleedthrough foreground is darker, reduce the blending alpha value
img_bleed_brightness = generate_average_intensity(img_bleed)
img_brightness = generate_average_intensity(img)
if img_bleed_brightness < img_brightness:
new_alpha = self.alpha * (img_bleed_brightness / img_brightness) / 2
else:
new_alpha = self.alpha
ob = OverlayBuilder(
"normal",
img_bleed_input,
img,
1,
(1, 1),
"center",
0,
new_alpha,
)
return ob.build_overlay()
[docs]
def generate_offset(self, img_bleed, offsets):
"""Offset image based on the input offset value so that bleedthrough effect is visible and not stacked with background image.
:param img_bleed: The input image to apply the offset function.
:type img_bleed: numpy.array (numpy.uint8)
:param offsets: The offset value.
:type offsets: int
"""
x_offset = offsets[0]
y_offset = offsets[1]
if (x_offset == 0) and (y_offset == 0):
return img_bleed
elif x_offset == 0:
img_bleed[y_offset:, :] = img_bleed[:-y_offset, :]
elif y_offset == 0:
img_bleed[:, x_offset:] = img_bleed[:, :-x_offset]
else:
img_bleed[y_offset:, x_offset:] = img_bleed[:-y_offset, :-x_offset]
return img_bleed
[docs]
def generate_bleeding_ink(self, img, intensity_range, color_range, ksize, sigmaX):
"""Preprocess and create bleeding ink effect in the input image.
:param img: The input image to apply the offset function.
:type img: numpy.array (numpy.uint8)
:param intensity_range: Pair of floats determining the range from which noise intensity is sampled.
:type intensity_range: tuple
:param color_range: Pair of ints determining the range from which color noise is sampled.
:type color_range: tuple
:param ksize: Tuple of height/width pairs from which to sample the kernel size. Higher value increases the spreadness of bleeding effect.
:type ksize: tuple
:param sigmaX: Standard deviation of the kernel along the x-axis.
:type sigmaX: float
"""
img_noise = np.double(
add_noise(img, intensity_range=intensity_range, color_range=color_range, noise_condition=1),
)
img_bleed = cv2.GaussianBlur(img_noise, ksize=ksize, sigmaX=sigmaX)
return img_bleed
# create foreground image for bleedthrough effect
[docs]
def create_bleedthrough_foreground(self, image: np.ndarray):
"""Create foreground image for bleedthrough effect.
:param image: The background image of the bleedthrough effect.
:type image: numpy.array (numpy.uint8)
"""
# load image from cache
image_bleedthrough_foreground = load_image_from_cache(random_image=1)
if image_bleedthrough_foreground is not None:
# resize foreground
image_bleedthrough_foreground = cv2.resize(
image_bleedthrough_foreground,
(image.shape[1], image.shape[0]),
interpolation=cv2.INTER_AREA,
)
else:
image_bleedthrough_foreground = image
# flip left-right only, flip top-bottom get inverted text, which is not realistic
image_bleedthrough_foreground = cv2.flip(image_bleedthrough_foreground, 1)
return image_bleedthrough_foreground
# Applies the Augmentation to input data.
def __call__(self, image, layer=None, force=False):
if force or self.should_run():
image = image.copy()
image_bleedthrough_foreground = self.create_bleedthrough_foreground(image)
image_bleed = self.generate_bleeding_ink(
image_bleedthrough_foreground,
self.intensity_range,
self.color_range,
self.ksize,
self.sigmaX,
)
image_bleed_offset = self.generate_offset(image_bleed, self.offsets)
image_bleedthrough = self.blend(image, image_bleed_offset, self.alpha)
return image_bleedthrough