import random
import cv2
import numpy as np
from sklearn.datasets import make_blobs
from augraphy.base.augmentation import Augmentation
[docs]
class Letterpress(Augmentation):
"""Produces regions of ink mimicking the effect of ink pressed unevenly onto paper.
:param n_samples: Pair of ints determining number of points in a cluster.
:type n_samples: tuple, optional
:param n_clusters: Pair of ints determining number of clusters.
:type n_clusters: tuple, optional
:param std_range: Pair of ints determining the range from which the
standard deviation of the blob distribution is sampled.
:type std_range: tuple, optional
:param value_range: Pair of ints determining the range from which the
value of a point in the blob is sampled.
:type value_range: tuple, optional
:param value_threshold_range: Min value of pixel to enable letterpress effect.
:type value_threshold_range: tuple, optional
:param blur: Flag to enable blur in letterpress noise mask.
:type blur: int, optional
:param p: The probability this Augmentation will be applied.
:type p: float, optional
"""
def __init__(
self,
n_samples=(300, 800),
n_clusters=(300, 800),
std_range=(1500, 5000),
value_range=(200, 255),
value_threshold_range=(128, 128),
blur=1,
p=1,
):
"""Constructor method"""
super().__init__(p=p)
self.n_samples = n_samples
self.n_clusters = n_clusters
self.std_range = std_range
self.value_range = value_range
self.value_threshold_range = value_threshold_range
self.blur = blur
def __repr__(self):
return f"Letterpress(n_samples={self.n_samples}, std_range={self.std_range}, value_range={self.value_range}, value_threshold_range={self.value_threshold_range}, blur={self.blur}, p={self.p})"
def __call__(self, image, layer=None, force=False):
if force or self.should_run():
image = image.copy()
ysize, xsize = image.shape[:2]
max_box_size = max(ysize, xsize)
generated_points = np.array([[-1, -1]], dtype="float")
for i in range(random.randint(8, 12)):
n_samples = [
random.randint(self.n_samples[0], self.n_samples[1])
for _ in range(random.randint(self.n_clusters[0], self.n_clusters[1]))
]
std = random.randint(self.std_range[0], self.std_range[1]) / 100
# generate clusters of blobs
generated_points_new, point_group = make_blobs(
n_samples=n_samples,
center_box=(0, max_box_size),
cluster_std=std,
n_features=2,
)
generated_points = np.concatenate((generated_points, generated_points_new), axis=0)
# remove decimals
generated_points = generated_points.astype("int")
# delete location where < 0 and > image size
ind_delete = np.logical_or.reduce(
(
generated_points[:, 0] < 0,
generated_points[:, 1] < 0,
generated_points[:, 0] > xsize - 1,
generated_points[:, 1] > ysize - 1,
),
)
generated_points_x = np.delete(generated_points[:, 0], ind_delete.reshape(ind_delete.shape[0]), axis=0)
generated_points_y = np.delete(generated_points[:, 1], ind_delete.reshape(ind_delete.shape[0]), axis=0)
# initialize empty noise mask and noise mask with random values
noise_mask = np.copy(image)
noise_mask2 = (np.random.random((image.shape[0], image.shape[1])) * 255).astype("uint8")
noise_mask2 = np.random.randint(
self.value_range[0],
self.value_range[1],
size=(image.shape[0], image.shape[1]),
dtype="uint8",
)
# insert noise value according to generate points
if len(image.shape) > 2:
for i in range(image.shape[2]):
noise_mask[generated_points_y, generated_points_x, i] = noise_mask2[
generated_points_y,
generated_points_x,
]
else:
noise_mask[generated_points_y, generated_points_x] = noise_mask2[generated_points_y, generated_points_x]
if self.blur:
# gaussian blur needs uint8 input
noise_mask = cv2.GaussianBlur(noise_mask, (5, 5), 0)
if self.value_threshold_range[1] >= self.value_threshold_range[0]:
value_threshold = random.randint(self.value_threshold_range[0], self.value_threshold_range[1])
else:
value_threshold = self.value_threshold_range[1]
# apply noise to image
indices = image < value_threshold
image[indices] = noise_mask[indices]
return image