Spaces:
Running on Zero
Running on Zero
Upload utils.py
Browse files
utils.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import random
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
from PIL import Image, ImageEnhance, ImageFilter
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def binarize_mask(mask, threshold=0):
|
| 10 |
+
return mask.point(lambda p: 255 if p > threshold else 0).convert("L")
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def square_crop_by_mask(image, mask, scale=1.2):
|
| 14 |
+
W, H = image.size
|
| 15 |
+
bbox = mask.getbbox()
|
| 16 |
+
if bbox is None:
|
| 17 |
+
cx, cy = W / 2.0, H / 2.0
|
| 18 |
+
side = float(min(W, H))
|
| 19 |
+
else:
|
| 20 |
+
l, u, r, lo = bbox
|
| 21 |
+
cx = (l + r) / 2.0
|
| 22 |
+
cy = (u + lo) / 2.0
|
| 23 |
+
side = max(r - l, lo - u) * scale
|
| 24 |
+
side_i = max(1, int(round(side)))
|
| 25 |
+
left = int(round(cx - side_i / 2.0))
|
| 26 |
+
top = int(round(cy - side_i / 2.0))
|
| 27 |
+
box = (left, top, left + side_i, top + side_i)
|
| 28 |
+
# PIL fills out-of-bounds crop regions with 0: black for the RGB image and
|
| 29 |
+
# "unmasked" (0) for the L mask - exactly the black padding we want.
|
| 30 |
+
return image.crop(box), mask.crop(box), box
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def process_source(source_image, source_mask, image_size=1024, crop_scale=1.2, binarize_threshold=0):
|
| 35 |
+
source_mask = binarize_mask(source_mask, binarize_threshold)
|
| 36 |
+
source_image_c, source_mask_c, crop_box = square_crop_by_mask(source_image, source_mask, crop_scale)
|
| 37 |
+
size = (image_size, image_size)
|
| 38 |
+
image = source_image_c.resize(size, Image.LANCZOS) # GT crop
|
| 39 |
+
mask = source_mask_c.resize(size, Image.NEAREST)
|
| 40 |
+
white = Image.new("RGB", size, (255, 255, 255))
|
| 41 |
+
# composite(white, image, mask): mask==255 -> white, mask==0 -> image
|
| 42 |
+
background_image = Image.composite(white, image, mask)
|
| 43 |
+
return background_image, image, crop_box, source_mask_c
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def process_reference(ref_image, ref_mask, image_size=1024, crop_scale=1.2,
|
| 47 |
+
binarize_threshold=0, augment=False, p_aug=0.8):
|
| 48 |
+
ref_mask = binarize_mask(ref_mask, binarize_threshold)
|
| 49 |
+
ref_image_c, ref_mask_c, _ = square_crop_by_mask(ref_image, ref_mask, crop_scale)
|
| 50 |
+
size = (image_size, image_size)
|
| 51 |
+
ref_image_r = ref_image_c.resize(size, Image.LANCZOS)
|
| 52 |
+
ref_mask_r = ref_mask_c.resize(size, Image.NEAREST)
|
| 53 |
+
white = Image.new("RGB", size, (255, 255, 255))
|
| 54 |
+
# composite(ref_image, white, mask): mask==255 -> object, mask==0 -> white
|
| 55 |
+
ref_image_out = Image.composite(ref_image_r, white, ref_mask_r)
|
| 56 |
+
if augment:
|
| 57 |
+
ref_image_out = augment_ref_image(ref_image_out, ref_mask_r, p_aug=p_aug)
|
| 58 |
+
return ref_image_out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def paste_back(generated_crop, source_image, crop_box, source_mask_cropped, feather=0):
|
| 62 |
+
left, top, right, bottom = crop_box
|
| 63 |
+
crop_side = right - left
|
| 64 |
+
generated_crop = generated_crop.resize((crop_side, crop_side), Image.LANCZOS)
|
| 65 |
+
original_crop = source_image.crop(crop_box)
|
| 66 |
+
if feather > 0:
|
| 67 |
+
source_mask_cropped = source_mask_cropped.filter(ImageFilter.GaussianBlur(feather))
|
| 68 |
+
edited_crop = Image.composite(generated_crop, original_crop, source_mask_cropped)
|
| 69 |
+
final_image = source_image.copy()
|
| 70 |
+
final_image.paste(edited_crop, (left, top))
|
| 71 |
+
return final_image
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _adjust_hue(img, shift):
|
| 75 |
+
"""Shift the hue of an RGB image by ``shift`` (in 0..255 units)."""
|
| 76 |
+
hsv = img.convert("HSV")
|
| 77 |
+
h, s, v = hsv.split()
|
| 78 |
+
h_arr = (np.asarray(h).astype(np.int16) + int(round(shift))) % 256
|
| 79 |
+
h = Image.fromarray(h_arr.astype(np.uint8), "L")
|
| 80 |
+
return Image.merge("HSV", (h, s, v)).convert("RGB")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def augment_ref_image(ref_image, mask, p_aug=0.8):
|
| 84 |
+
img = ref_image.convert("RGB").copy()
|
| 85 |
+
m = mask.convert("L")
|
| 86 |
+
|
| 87 |
+
# Horizontal flip is safe: the object sits on a uniform white background.
|
| 88 |
+
if random.random() < 0.5:
|
| 89 |
+
img = img.transpose(Image.FLIP_LEFT_RIGHT)
|
| 90 |
+
m = m.transpose(Image.FLIP_LEFT_RIGHT)
|
| 91 |
+
|
| 92 |
+
# Colour jitter on the whole image; background is re-whitened below.
|
| 93 |
+
if random.random() < p_aug:
|
| 94 |
+
img = ImageEnhance.Brightness(img).enhance(random.uniform(0.85, 1.15))
|
| 95 |
+
if random.random() < p_aug:
|
| 96 |
+
img = ImageEnhance.Contrast(img).enhance(random.uniform(0.85, 1.15))
|
| 97 |
+
if random.random() < p_aug:
|
| 98 |
+
img = ImageEnhance.Color(img).enhance(random.uniform(0.85, 1.15))
|
| 99 |
+
if random.random() < p_aug:
|
| 100 |
+
img = ImageEnhance.Sharpness(img).enhance(random.uniform(0.9, 1.1))
|
| 101 |
+
if random.random() < p_aug:
|
| 102 |
+
img = _adjust_hue(img, random.uniform(-0.06, 0.06) * 255.0)
|
| 103 |
+
|
| 104 |
+
# Keep the (jittered) object, reset everything outside the mask to white.
|
| 105 |
+
white = Image.new("RGB", img.size, (255, 255, 255))
|
| 106 |
+
return Image.composite(img, white, m)
|