|
import numpy as np |
|
import random |
|
from PIL import Image, ImageDraw |
|
|
|
|
|
def get_mask_by_input_strokes( |
|
init_points, imageWidth=320, imageHeight=180, nStroke=5, |
|
nVertexBound=[10, 30], maxHeadSpeed=15, maxHeadAcceleration=(15, 0.5), |
|
brushWidthBound=(5, 20), boarderGap=None, nMovePointRatio=0.5, maxPiontMove=10, |
|
maxLineAcceleration=5, maxInitSpeed=5 |
|
): |
|
''' |
|
Get video masks by random strokes which move randomly between each |
|
frame, including the whole stroke and its control points |
|
|
|
Parameters |
|
---------- |
|
imageWidth: Image width |
|
imageHeight: Image height |
|
nStroke: Number of drawed lines |
|
nVertexBound: Lower/upper bound of number of control points for each line |
|
maxHeadSpeed: Max head speed when creating control points |
|
maxHeadAcceleration: Max acceleration applying on the current head point ( |
|
a head point and its velosity decides the next point) |
|
brushWidthBound (min, max): Bound of width for each stroke |
|
boarderGap: The minimum gap between image boarder and drawed lines |
|
nMovePointRatio: The ratio of control points to move for next frames |
|
maxPiontMove: The magnitude of movement for control points for next frames |
|
maxLineAcceleration: The magnitude of acceleration for the whole line |
|
|
|
Examples |
|
---------- |
|
object_like_setting = { |
|
"nVertexBound": [5, 20], |
|
"maxHeadSpeed": 15, |
|
"maxHeadAcceleration": (15, 3.14), |
|
"brushWidthBound": (30, 50), |
|
"nMovePointRatio": 0.5, |
|
"maxPiontMove": 10, |
|
"maxLineAcceleration": (5, 0.5), |
|
"boarderGap": 20, |
|
"maxInitSpeed": 10, |
|
} |
|
rand_curve_setting = { |
|
"nVertexBound": [10, 30], |
|
"maxHeadSpeed": 20, |
|
"maxHeadAcceleration": (15, 0.5), |
|
"brushWidthBound": (3, 10), |
|
"nMovePointRatio": 0.5, |
|
"maxPiontMove": 3, |
|
"maxLineAcceleration": (5, 0.5), |
|
"boarderGap": 20, |
|
"maxInitSpeed": 6 |
|
} |
|
get_video_masks_by_moving_random_stroke(video_len=5, nStroke=3, **object_like_setting) |
|
''' |
|
|
|
mask = Image.new(mode='1', size=(imageWidth, imageHeight), color=1) |
|
control_points_set = [] |
|
for i in range(nStroke): |
|
brushWidth = np.random.randint(brushWidthBound[0], brushWidthBound[1]) |
|
Xs, Ys, velocity = get_random_stroke_control_points( |
|
init_point=init_points[i], |
|
imageWidth=imageWidth, imageHeight=imageHeight, |
|
nVertexBound=nVertexBound, maxHeadSpeed=maxHeadSpeed, |
|
maxHeadAcceleration=maxHeadAcceleration, boarderGap=boarderGap, |
|
maxInitSpeed=maxInitSpeed |
|
) |
|
control_points_set.append((Xs, Ys, velocity, brushWidth)) |
|
draw_mask_by_control_points(mask, Xs, Ys, brushWidth, fill=0) |
|
|
|
|
|
mask = Image.new(mode='1', size=(imageWidth, imageHeight), color=1) |
|
for j in range(len(control_points_set)): |
|
Xs, Ys, velocity, brushWidth = control_points_set[j] |
|
new_Xs, new_Ys = random_move_control_points( |
|
Xs, Ys, velocity, nMovePointRatio, maxPiontMove, |
|
maxLineAcceleration, boarderGap |
|
) |
|
control_points_set[j] = (new_Xs, new_Ys, velocity, brushWidth) |
|
for Xs, Ys, velocity, brushWidth in control_points_set: |
|
draw_mask_by_control_points(mask, Xs, Ys, brushWidth, fill=0) |
|
|
|
return np.array(mask) |
|
|
|
|
|
def random_accelerate(velocity, maxAcceleration, dist='uniform'): |
|
speed, angle = velocity |
|
d_speed, d_angle = maxAcceleration |
|
|
|
if dist == 'uniform': |
|
speed += np.random.uniform(-d_speed, d_speed) |
|
angle += np.random.uniform(-d_angle, d_angle) |
|
elif dist == 'guassian': |
|
speed += np.random.normal(0, d_speed / 2) |
|
angle += np.random.normal(0, d_angle / 2) |
|
else: |
|
raise NotImplementedError(f'Distribution type {dist} is not supported.') |
|
|
|
return (speed, angle) |
|
|
|
|
|
def random_move_control_points(Xs, Ys, lineVelocity, nMovePointRatio, maxPiontMove, maxLineAcceleration, boarderGap=15): |
|
new_Xs = Xs.copy() |
|
new_Ys = Ys.copy() |
|
|
|
|
|
speed, angle = lineVelocity |
|
new_Xs += int(speed * np.cos(angle)) |
|
new_Ys += int(speed * np.sin(angle)) |
|
lineVelocity = random_accelerate(lineVelocity, maxLineAcceleration, dist='guassian') |
|
|
|
|
|
chosen = np.arange(len(Xs)) |
|
np.random.shuffle(chosen) |
|
chosen = chosen[:int(len(Xs) * nMovePointRatio)] |
|
for i in chosen: |
|
new_Xs[i] += np.random.randint(-maxPiontMove, maxPiontMove) |
|
new_Ys[i] += np.random.randint(-maxPiontMove, maxPiontMove) |
|
return new_Xs, new_Ys |
|
|
|
|
|
def get_random_stroke_control_points( |
|
init_point, |
|
imageWidth, imageHeight, |
|
nVertexBound=(10, 30), maxHeadSpeed=10, maxHeadAcceleration=(5, 0.5), boarderGap=20, |
|
maxInitSpeed=10 |
|
): |
|
''' |
|
Implementation the free-form training masks generating algorithm |
|
proposed by JIAHUI YU et al. in "Free-Form Image Inpainting with Gated Convolution" |
|
''' |
|
startX = init_point[0] |
|
startY = init_point[1] |
|
|
|
Xs = [init_point[0]] |
|
Ys = [init_point[1]] |
|
|
|
numVertex = np.random.randint(nVertexBound[0], nVertexBound[1]) |
|
|
|
angle = np.random.uniform(0, 2 * np.pi) |
|
speed = np.random.uniform(0, maxHeadSpeed) |
|
|
|
for i in range(numVertex): |
|
speed, angle = random_accelerate((speed, angle), maxHeadAcceleration) |
|
speed = np.clip(speed, 0, maxHeadSpeed) |
|
|
|
nextX = startX + speed * np.sin(angle) |
|
nextY = startY + speed * np.cos(angle) |
|
|
|
if boarderGap is not None: |
|
nextX = np.clip(nextX, boarderGap, imageWidth - boarderGap) |
|
nextY = np.clip(nextY, boarderGap, imageHeight - boarderGap) |
|
|
|
startX, startY = nextX, nextY |
|
Xs.append(nextX) |
|
Ys.append(nextY) |
|
|
|
velocity = get_random_velocity(maxInitSpeed, dist='guassian') |
|
|
|
return np.array(Xs), np.array(Ys), velocity |
|
|
|
|
|
def get_random_velocity(max_speed, dist='uniform'): |
|
if dist == 'uniform': |
|
speed = np.random.uniform(max_speed) |
|
elif dist == 'guassian': |
|
speed = np.abs(np.random.normal(0, max_speed / 2)) |
|
else: |
|
raise NotImplementedError(f'Distribution type {dist} is not supported.') |
|
|
|
angle = np.random.uniform(0, 2 * np.pi) |
|
return (speed, angle) |
|
|
|
|
|
def draw_mask_by_control_points(mask, Xs, Ys, brushWidth, fill=255): |
|
radius = brushWidth // 2 - 1 |
|
for i in range(1, len(Xs)): |
|
draw = ImageDraw.Draw(mask) |
|
startX, startY = Xs[i - 1], Ys[i - 1] |
|
nextX, nextY = Xs[i], Ys[i] |
|
draw.line((startX, startY) + (nextX, nextY), fill=fill, width=brushWidth) |
|
for x, y in zip(Xs, Ys): |
|
draw.ellipse((x - radius, y - radius, x + radius, y + radius), fill=fill) |
|
return mask |
|
|
|
|
|
|
|
def get_random_walk_mask(imageWidth=320, imageHeight=180, length=None): |
|
action_list = [[0, 1], [0, -1], [1, 0], [-1, 0]] |
|
canvas = np.zeros((imageHeight, imageWidth)).astype("i") |
|
if length is None: |
|
length = imageWidth * imageHeight |
|
x = random.randint(0, imageHeight - 1) |
|
y = random.randint(0, imageWidth - 1) |
|
x_list = [] |
|
y_list = [] |
|
for i in range(length): |
|
r = random.randint(0, len(action_list) - 1) |
|
x = np.clip(x + action_list[r][0], a_min=0, a_max=imageHeight - 1) |
|
y = np.clip(y + action_list[r][1], a_min=0, a_max=imageWidth - 1) |
|
x_list.append(x) |
|
y_list.append(y) |
|
canvas[np.array(x_list), np.array(y_list)] = 1 |
|
return Image.fromarray(canvas * 255).convert('1') |
|
|
|
|
|
def get_masked_ratio(mask): |
|
""" |
|
Calculate the masked ratio. |
|
mask: Expected a binary PIL image, where 0 and 1 represent |
|
masked(invalid) and valid pixel values. |
|
""" |
|
hist = mask.histogram() |
|
return hist[0] / np.prod(mask.size) |
|
|