|
try: |
|
import cv2 |
|
except: |
|
pass |
|
import numpy as np |
|
from PIL import Image |
|
from relighting.ball_processor import get_ideal_normal_ball |
|
|
|
def create_grid(image_size, n_ball, size): |
|
height, width = image_size |
|
nx, ny = n_ball |
|
if nx * ny == 1: |
|
grid = np.array([[(height-size)//2, (width-size)//2]]) |
|
else: |
|
height_ = np.linspace(0, height-size, nx).astype(int) |
|
weight_ = np.linspace(0, width-size, ny).astype(int) |
|
hh, ww = np.meshgrid(height_, weight_) |
|
grid = np.stack([hh,ww], axis = -1).reshape(-1,2) |
|
|
|
return grid |
|
|
|
class MaskGenerator(): |
|
def __init__(self, cache_mask=True): |
|
self.cache_mask = cache_mask |
|
self.all_masks = [] |
|
|
|
def clear_cache(self): |
|
self.all_masks = [] |
|
|
|
def retrieve_masks(self): |
|
return self.all_masks |
|
|
|
def generate_grid(self, image, mask_ball, n_ball=16, size=128): |
|
ball_positions = create_grid(image.size, n_ball, size) |
|
|
|
|
|
masks = [] |
|
mask_template = np.zeros(image.size) |
|
for x, y in ball_positions: |
|
mask = mask_template.copy() |
|
mask[y:y+size, x:x+size] = 255 * mask_ball |
|
mask = Image.fromarray(mask.astype(np.uint8), "L") |
|
masks.append(mask) |
|
|
|
|
|
|
|
|
|
return masks, ball_positions |
|
|
|
def generate_single(self, image, mask_ball, x, y, size): |
|
w,h = image.size |
|
mask = np.zeros((h,w)) |
|
mask[y:y+size, x:x+size] = 255 * mask_ball |
|
mask = Image.fromarray(mask.astype(np.uint8), "L") |
|
|
|
return mask |
|
|
|
def generate_best(self, image, mask_ball, size): |
|
w,h = image.size |
|
mask = np.zeros((h,w)) |
|
|
|
(y, x), _ = find_best_location(np.array(image), ball_size=size) |
|
mask[y:y+size, x:x+size] = 255 * mask_ball |
|
mask = Image.fromarray(mask.astype(np.uint8), "L") |
|
|
|
return mask, (x, y) |
|
|
|
|
|
def get_only_high_freqency(image: np.array): |
|
""" |
|
Get only height freqency image by subtract low freqency (using gaussian blur) |
|
@params image: np.array - image in RGB format [h,w,3] |
|
@return high_frequency: np.array - high freqnecy image in grayscale format [h,w] |
|
""" |
|
|
|
|
|
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) |
|
|
|
|
|
kernel_size = 11 |
|
high_frequency = gray - cv2.GaussianBlur(gray,(kernel_size, kernel_size), 0) |
|
|
|
return high_frequency |
|
|
|
def find_best_location(image, ball_size=128): |
|
""" |
|
Find the best location to place the ball (Eg. empty location) |
|
@params image: np.array - image in RGB format [h,w,3] |
|
@return min_pos: tuple - top left position of the best location (the location is in "Y,X" format) |
|
@return min_val: float - the sum value contain in the window |
|
""" |
|
local_variance = get_only_high_freqency(image) |
|
qsum = quicksum2d(local_variance) |
|
|
|
min_val = None |
|
min_pos = None |
|
k = ball_size |
|
for i in range(k-1, qsum.shape[0]): |
|
for j in range(k-1, qsum.shape[1]): |
|
A = 0 if i-k < 0 else qsum[i-k, j] |
|
B = 0 if j-k < 0 else qsum[i, j-k] |
|
C = 0 if (i-k < 0) or (j-k < 0) else qsum[i-k, j-k] |
|
sum = qsum[i, j] - A - B + C |
|
if (min_val is None) or (sum < min_val): |
|
min_val = sum |
|
min_pos = (i-k+1, j-k+1) |
|
|
|
return min_pos, min_val |
|
|
|
def quicksum2d(x: np.array): |
|
""" |
|
Quick sum algorithm to find the window that have smallest sum with O(n^2) complexity |
|
@params x: np.array - image in grayscale [h,w] |
|
@return q: np.array - quick sum of the image for future seach in find_best_location [h,w] |
|
""" |
|
qsum = np.zeros(x.shape) |
|
for i in range(x.shape[0]): |
|
for j in range(x.shape[1]): |
|
A = 0 if i-1 < 0 else qsum[i-1, j] |
|
B = 0 if j-1 < 0 else qsum[i, j-1] |
|
C = 0 if (i-1 < 0) or (j-1 < 0) else qsum[i-1, j-1] |
|
qsum[i, j] = A + B - C + x[i, j] |
|
|
|
return qsum |