Spaces:
Sleeping
Sleeping
from __future__ import absolute_import, division, print_function, unicode_literals | |
import scipy.ndimage | |
from scipy.sparse.linalg import spsolve | |
from scipy import sparse | |
import scipy.io as sio | |
import numpy as np | |
from PIL import Image | |
import copy | |
import cv2 | |
import os | |
import argparse | |
def sub2ind(pi, pj, imgH, imgW): | |
return pj + pi * imgW | |
def Poisson_blend(imgTrg, imgSrc_gx, imgSrc_gy, holeMask, edge=None): | |
imgH, imgW, nCh = imgTrg.shape | |
if not isinstance(edge, np.ndarray): | |
edge = np.zeros((imgH, imgW), dtype=np.float32) | |
# Initialize the reconstructed image | |
imgRecon = np.zeros((imgH, imgW, nCh), dtype=np.float32) | |
# prepare discrete Poisson equation | |
A, b = solvePoisson(holeMask, imgSrc_gx, imgSrc_gy, imgTrg, edge) | |
# Independently process each channel | |
for ch in range(nCh): | |
# solve Poisson equation | |
x = scipy.sparse.linalg.lsqr(A, b[:, ch, None])[0] | |
imgRecon[:, :, ch] = x.reshape(imgH, imgW) | |
# Combined with the known region in the target | |
holeMaskC = np.tile(np.expand_dims(holeMask, axis=2), (1, 1, nCh)) | |
imgBlend = holeMaskC * imgRecon + (1 - holeMaskC) * imgTrg | |
# Fill in edge pixel | |
pi = np.expand_dims(np.where((holeMask * edge) == 1)[0], axis=1) # y, i | |
pj = np.expand_dims(np.where((holeMask * edge) == 1)[1], axis=1) # x, j | |
for k in range(len(pi)): | |
if pi[k, 0] - 1 >= 0: | |
if edge[pi[k, 0] - 1, pj[k, 0]] == 0: | |
imgBlend[pi[k, 0], pj[k, 0], :] = imgBlend[pi[k, 0] - 1, pj[k, 0], :] | |
continue | |
if pi[k, 0] + 1 <= imgH - 1: | |
if edge[pi[k, 0] + 1, pj[k, 0]] == 0: | |
imgBlend[pi[k, 0], pj[k, 0], :] = imgBlend[pi[k, 0] + 1, pj[k, 0], :] | |
continue | |
if pj[k, 0] - 1 >= 0: | |
if edge[pi[k, 0], pj[k, 0] - 1] == 0: | |
imgBlend[pi[k, 0], pj[k, 0], :] = imgBlend[pi[k, 0], pj[k, 0] - 1, :] | |
continue | |
if pj[k, 0] + 1 <= imgW - 1: | |
if edge[pi[k, 0], pj[k, 0] + 1] == 0: | |
imgBlend[pi[k, 0], pj[k, 0], :] = imgBlend[pi[k, 0], pj[k, 0] + 1, :] | |
return imgBlend | |
def solvePoisson(holeMask, imgSrc_gx, imgSrc_gy, imgTrg, edge): | |
# Prepare the linear system of equations for Poisson blending | |
imgH, imgW = holeMask.shape | |
N = imgH * imgW | |
# Number of unknown variables | |
numUnknownPix = holeMask.sum() | |
# 4-neighbors: dx and dy | |
dx = [1, 0, -1, 0] | |
dy = [0, 1, 0, -1] | |
# 3 | |
# | | |
# 2 -- * -- 0 | |
# | | |
# 1 | |
# | |
# Initialize (I, J, S), for sparse matrix A where A(I(k), J(k)) = S(k) | |
I = np.empty((0, 1), dtype=np.float32) | |
J = np.empty((0, 1), dtype=np.float32) | |
S = np.empty((0, 1), dtype=np.float32) | |
# Initialize b | |
b = np.empty((0, 2), dtype=np.float32) | |
# Precompute unkonwn pixel position | |
pi = np.expand_dims(np.where(holeMask == 1)[0], axis=1) # y, i | |
pj = np.expand_dims(np.where(holeMask == 1)[1], axis=1) # x, j | |
pind = sub2ind(pi, pj, imgH, imgW) | |
# |--------------------| | |
# | y (i) | | |
# | x (j) * | | |
# | | | |
# |--------------------| | |
qi = np.concatenate((pi + dy[0], | |
pi + dy[1], | |
pi + dy[2], | |
pi + dy[3]), axis=1) | |
qj = np.concatenate((pj + dx[0], | |
pj + dx[1], | |
pj + dx[2], | |
pj + dx[3]), axis=1) | |
# Handling cases at image borders | |
validN = (qi >= 0) & (qi <= imgH - 1) & (qj >= 0) & (qj <= imgW - 1) | |
qind = np.zeros((validN.shape), dtype=np.float32) | |
qind[validN] = sub2ind(qi[validN], qj[validN], imgH, imgW) | |
e_start = 0 # equation counter start | |
e_stop = 0 # equation stop | |
# 4 neighbors | |
I, J, S, b, e_start, e_stop = constructEquation(0, validN, holeMask, edge, imgSrc_gx, imgSrc_gy, imgTrg, pi, pj, pind, qi, qj, qind, I, J, S, b, e_start, e_stop) | |
I, J, S, b, e_start, e_stop = constructEquation(1, validN, holeMask, edge, imgSrc_gx, imgSrc_gy, imgTrg, pi, pj, pind, qi, qj, qind, I, J, S, b, e_start, e_stop) | |
I, J, S, b, e_start, e_stop = constructEquation(2, validN, holeMask, edge, imgSrc_gx, imgSrc_gy, imgTrg, pi, pj, pind, qi, qj, qind, I, J, S, b, e_start, e_stop) | |
I, J, S, b, e_start, e_stop = constructEquation(3, validN, holeMask, edge, imgSrc_gx, imgSrc_gy, imgTrg, pi, pj, pind, qi, qj, qind, I, J, S, b, e_start, e_stop) | |
nEqn = len(b) | |
# Construct the sparse matrix A | |
A = sparse.csr_matrix((S[:, 0], (I[:, 0], J[:, 0])), shape=(nEqn, N)) | |
return A, b | |
def constructEquation(n, validN, holeMask, edge, imgSrc_gx, imgSrc_gy, imgTrg, pi, pj, pind, qi, qj, qind, I, J, S, b, e_start, e_stop): | |
# Pixel that has valid neighbors | |
validNeighbor = validN[:, n] | |
# Change the out-of-boundary value to 0, in order to run edge[y,x] | |
# in the next line. It won't affect anything as validNeighbor is saved already | |
qi_tmp = copy.deepcopy(qi) | |
qj_tmp = copy.deepcopy(qj) | |
qi_tmp[np.invert(validNeighbor), n] = 0 | |
qj_tmp[np.invert(validNeighbor), n] = 0 | |
# Not edge | |
NotEdge = (edge[pi[:, 0], pj[:, 0]] == 0) * (edge[qi_tmp[:, n], qj_tmp[:, n]] == 0) | |
# Boundary constraint | |
Boundary = holeMask[qi_tmp[:, n], qj_tmp[:, n]] == 0 | |
valid = validNeighbor * NotEdge * Boundary | |
J_tmp = pind[valid, :] | |
# num of equations: len(J_tmp) | |
e_stop = e_start + len(J_tmp) | |
I_tmp = np.arange(e_start, e_stop, dtype=np.float32).reshape(-1, 1) | |
e_start = e_stop | |
S_tmp = np.ones(J_tmp.shape, dtype=np.float32) | |
if n == 0: | |
b_tmp = - imgSrc_gx[pi[valid, 0], pj[valid, 0], :] + imgTrg[qi[valid, n], qj[valid, n], :] | |
elif n == 2: | |
b_tmp = imgSrc_gx[pi[valid, 0], pj[valid, 0] - 1, :] + imgTrg[qi[valid, n], qj[valid, n], :] | |
elif n == 1: | |
b_tmp = - imgSrc_gy[pi[valid, 0], pj[valid, 0], :] + imgTrg[qi[valid, n], qj[valid, n], :] | |
elif n == 3: | |
b_tmp = imgSrc_gy[pi[valid, 0] - 1, pj[valid, 0], :] + imgTrg[qi[valid, n], qj[valid, n], :] | |
I = np.concatenate((I, I_tmp)) | |
J = np.concatenate((J, J_tmp)) | |
S = np.concatenate((S, S_tmp)) | |
b = np.concatenate((b, b_tmp)) | |
# Non-boundary constraint | |
NonBoundary = holeMask[qi_tmp[:, n], qj_tmp[:, n]] == 1 | |
valid = validNeighbor * NotEdge * NonBoundary | |
J_tmp = pind[valid, :] | |
# num of equations: len(J_tmp) | |
e_stop = e_start + len(J_tmp) | |
I_tmp = np.arange(e_start, e_stop, dtype=np.float32).reshape(-1, 1) | |
e_start = e_stop | |
S_tmp = np.ones(J_tmp.shape, dtype=np.float32) | |
if n == 0: | |
b_tmp = - imgSrc_gx[pi[valid, 0], pj[valid, 0], :] | |
elif n == 2: | |
b_tmp = imgSrc_gx[pi[valid, 0], pj[valid, 0] - 1, :] | |
elif n == 1: | |
b_tmp = - imgSrc_gy[pi[valid, 0], pj[valid, 0], :] | |
elif n == 3: | |
b_tmp = imgSrc_gy[pi[valid, 0] - 1, pj[valid, 0], :] | |
I = np.concatenate((I, I_tmp)) | |
J = np.concatenate((J, J_tmp)) | |
S = np.concatenate((S, S_tmp)) | |
b = np.concatenate((b, b_tmp)) | |
S_tmp = - np.ones(J_tmp.shape, dtype=np.float32) | |
J_tmp = qind[valid, n, None] | |
I = np.concatenate((I, I_tmp)) | |
J = np.concatenate((J, J_tmp)) | |
S = np.concatenate((S, S_tmp)) | |
return I, J, S, b, e_start, e_stop | |
def gradient_mask(mask): #产生梯度的mask | |
gradient_mask = np.logical_or.reduce((mask, | |
np.concatenate((mask[1:, :], np.zeros((1, mask.shape[1]), dtype=np.bool)), axis=0), | |
np.concatenate((mask[:, 1:], np.zeros((mask.shape[0], 1), dtype=np.bool)), axis=1))) | |
return gradient_mask | |
if __name__ == '__main__': | |
import cvbase | |
from skimage.feature import canny | |
import argparse | |
import imageio | |
parser = argparse.ArgumentParser() | |
parser.add_argument('--flow', type=str, default='../../test_blending/flow/00000.flo') | |
parser.add_argument('--mask', type=str, default='../../test_blending/mask/00000.png') | |
parser.add_argument('--width', type=int, default=432) | |
parser.add_argument('--height', type=int, default=256) | |
parser.add_argument('--output', type=str, default='../../test_blending/ret') | |
args = parser.parse_args() | |
flow, mask = args.flow, args.mask | |
width, height = args.width, args.height | |
output = args.output | |
if not os.path.exists(output): | |
os.makedirs(output) | |
flow = cvbase.read_flow(flow) | |
mask = cv2.imread(mask, 0) | |
h, w, c = flow.shape | |
flow_resized = np.zeros((height, width, 2)) | |
flow_resized[:, :, 0] = cv2.resize(flow[:, :, 0], (width, height), cv2.INTER_LINEAR) * width / w | |
flow_resized[:, :, 1] = cv2.resize(flow[:, :, 1], (width, height), cv2.INTER_LINEAR) * height / h | |
flow = flow_resized | |
mask = cv2.resize(mask, (width, height), cv2.INTER_NEAREST) | |
mask_gradient = gradient_mask(mask) | |
flow_gray = (flow[:, :, 0] ** 2 + flow[:, :, 1] ** 2) ** 0.5 | |
flow_gray = flow_gray / flow_gray.max() | |
edge = canny(flow_gray, sigma=1, low_threshold=0.1, high_threshold=0.2) | |
edge = edge.astype(np.bool) | |
masked_edge = edge * mask | |
# gradients | |
gradient_x = np.concatenate((np.diff(flow, axis=1), np.zeros((height, 1, 2), dtype=np.float32)), axis=1) | |
gradient_y = np.concatenate((np.diff(flow, axis=0), np.zeros((1, width, 2), dtype=np.float32)), axis=0) | |
gradient = np.concatenate((gradient_x, gradient_y), axis=2) | |
gradient[mask_gradient, :] = 0 # 把中间的梯度设置成了0 | |
# complete flow | |
imgSrc_gy = gradient[:, :, 2: 4] | |
imgSrc_gy = imgSrc_gy[0: h - 1, :, :] | |
imgSrc_gx = gradient[:, :, 0: 2] | |
imgSrc_gx = imgSrc_gx[:, 0: w - 1, :] | |
compFlow = Poisson_blend(flow, imgSrc_gx, imgSrc_gy, mask, masked_edge) # todo: edge or masked_edge ? | |
# save flow | |
flow_n = cvbase.flow2rgb(flow) | |
compFlow_n = cvbase.flow2rgb(compFlow) | |
imageio.imwrite(os.path.join(output, 'flow.png'), flow_n) | |
imageio.imwrite(os.path.join(output, 'compFlow.png'), compFlow_n) | |
imageio.imwrite(os.path.join(output, 'edge.png'), masked_edge) | |
# imageio.imwrite(os.path.join(output, 'gx.png'), imgSrc_gx) | |
# imageio.imwrite(os.path.join(output, 'gy.png'), imgSrc_gy) | |
imageio.imwrite(os.path.join(output, 'mask.png'), mask) | |
imageio.imwrite(os.path.join(output, 'grad0.png'), gradient[:, :, 0]) | |
imageio.imwrite(os.path.join(output, 'grad1.png'), gradient[:, :, 1]) | |