Skip to content

Instantly share code, notes, and snippets.

@andersource
Created May 30, 2024 12:07
Show Gist options
  • Save andersource/8e2ffa2382fca01f176420f54332ba22 to your computer and use it in GitHub Desktop.
Save andersource/8e2ffa2382fca01f176420f54332ba22 to your computer and use it in GitHub Desktop.
Spraying Digital Graffiti
import numpy as np
from skimage.io import imread, imsave
from skimage.filters.edges import sobel
from skimage.color import rgb2gray
from skimage.transform import rescale
from scipy import ndimage
from skimage import measure
from skimage import graph
def main():
DOWNSCALE_FACTOR = .3
im1 = rescale(imread("/path/to/wall.jpg"), DOWNSCALE_FACTOR, channel_axis=-1)
im2 = rescale(imread("/path/to/painted_wall.jpg"), DOWNSCALE_FACTOR, channel_axis=-1)
mask = rescale(imread("/path/to/binary_mask.png")[..., 0] < 100, DOWNSCALE_FACTOR, order=0)
edgemap = sobel(rgb2gray(im1))
contours = measure.find_contours(mask)
contours = [
fix_contour(contour.round().astype(int), edgemap)
for contour in contours
]
final_mask = np.zeros(edgemap.shape, int)
for contour in contours:
final_mask = np.maximum(fill_contour(edgemap.shape, contour), final_mask)
final_mask = final_mask.reshape((*final_mask.shape, 1))
res = final_mask * im2 + (1 - final_mask) * im1
imsave("/path/to/result.png", res)
def fix_contour(contour, edgemap, weight_coef=1):
edge_weight = 50
if np.std(contour, axis=0).mean() < 16:
edge_weight = 42
temp = np.zeros(edgemap.shape, bool)
temp[contour[:, 0], contour[:, 1]] = 1
dist = ndimage.distance_transform_edt(~temp)
sample_indices = sample_contour(contour, edgemap)
cost = dist + edgemap * edge_weight * weight_coef
total_path = []
for i in range(sample_indices.shape[0] - 1):
p, _ = graph.route_through_array(cost, contour[sample_indices[i]], contour[sample_indices[i + 1]], geometric=False)
p = np.array(p)
total_path.append(p)
p, _ = graph.route_through_array(cost, contour[sample_indices[-1]], contour[sample_indices[0]], geometric=False)
p = np.array(p)
total_path.append(p)
p = np.concatenate(total_path)
return p[:, 0], p[:, 1]
def fill_contour(shape, contour):
res = np.zeros(shape, dtype=bool)
res[contour] = 1
res = ndimage.binary_fill_holes(res)
return res
def sample_contour(contour, edgemap, N=100):
max_deviation = 7 if np.std(contour, axis=0).mean() < 16 else 20
indices = (np.arange(N) * contour.shape[0] / N).round().astype(int)
for i in range(indices.shape[0]):
idx_alternatives = np.clip(indices[i] + np.arange(-max_deviation, max_deviation + 1), 0, contour.shape[0] - 1)
indices[i] = idx_alternatives[edgemap[contour[idx_alternatives][:, 0], contour[idx_alternatives][:, 1]].argmin()]
return indices
if __name__ == "__main__":
main()
numpy
scipy
scikit-image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment