Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import scipy as sp
|
4 |
+
import scipy.sparse.linalg
|
5 |
+
import gradio as gr
|
6 |
+
import os
|
7 |
+
|
8 |
+
def get_image(img, mask=False):
|
9 |
+
if mask:
|
10 |
+
return np.where(img > 127, 1, 0)
|
11 |
+
return cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype('double') / 255.0
|
12 |
+
|
13 |
+
def neighbours(i, j, max_i, max_j):
|
14 |
+
pairs = []
|
15 |
+
for n in [-1, 1]:
|
16 |
+
if 0 <= i+n <= max_i:
|
17 |
+
pairs.append((i+n, j))
|
18 |
+
if 0 <= j+n <= max_j:
|
19 |
+
pairs.append((i, j+n))
|
20 |
+
return pairs
|
21 |
+
|
22 |
+
def poisson_blend(img_s, mask, img_t):
|
23 |
+
img_s_h, img_s_w = img_s.shape
|
24 |
+
|
25 |
+
nnz = (mask>0).sum()
|
26 |
+
im2var = -np.ones(mask.shape[0:2], dtype='int32')
|
27 |
+
im2var[mask>0] = np.arange(nnz)
|
28 |
+
|
29 |
+
ys, xs = np.where(mask==1)
|
30 |
+
|
31 |
+
A = sp.sparse.lil_matrix((4*nnz, nnz))
|
32 |
+
b = np.zeros(4*nnz)
|
33 |
+
|
34 |
+
e = 0
|
35 |
+
for n in range(nnz):
|
36 |
+
y, x = ys[n], xs[n]
|
37 |
+
|
38 |
+
for n_y, n_x in neighbours(y, x, img_s_h-1, img_s_w-1):
|
39 |
+
A[e, im2var[y][x]] = 1
|
40 |
+
b[e] = img_s[y][x] - img_s[n_y][n_x]
|
41 |
+
|
42 |
+
if im2var[n_y][n_x] != -1:
|
43 |
+
A[e, im2var[n_y][n_x]] = -1
|
44 |
+
else:
|
45 |
+
b[e] += img_t[n_y][n_x]
|
46 |
+
e += 1
|
47 |
+
|
48 |
+
A = sp.sparse.csr_matrix(A)
|
49 |
+
v = sp.sparse.linalg.lsqr(A, b)[0]
|
50 |
+
|
51 |
+
img_t_out = img_t.copy()
|
52 |
+
|
53 |
+
for n in range(nnz):
|
54 |
+
y, x = ys[n], xs[n]
|
55 |
+
img_t_out[y][x] = v[im2var[y][x]]
|
56 |
+
|
57 |
+
return np.clip(img_t_out, 0, 1)
|
58 |
+
|
59 |
+
def mixed_blend(img_s, mask, img_t):
|
60 |
+
img_s_h, img_s_w = img_s.shape
|
61 |
+
|
62 |
+
nnz = (mask>0).sum()
|
63 |
+
im2var = -np.ones(mask.shape[0:2], dtype='int32')
|
64 |
+
im2var[mask>0] = np.arange(nnz)
|
65 |
+
|
66 |
+
ys, xs = np.where(mask==1)
|
67 |
+
|
68 |
+
A = sp.sparse.lil_matrix((4*nnz, nnz))
|
69 |
+
b = np.zeros(4*nnz)
|
70 |
+
|
71 |
+
e = 0
|
72 |
+
for n in range(nnz):
|
73 |
+
y, x = ys[n], xs[n]
|
74 |
+
|
75 |
+
for n_y, n_x in neighbours(y, x, img_s_h-1, img_s_w-1):
|
76 |
+
ds = img_s[y][x] - img_s[n_y][n_x]
|
77 |
+
dt = img_t[y][x] - img_t[n_y][n_x]
|
78 |
+
d = ds if abs(ds) > abs(dt) else dt
|
79 |
+
|
80 |
+
A[e, im2var[y][x]] = 1
|
81 |
+
b[e] = d
|
82 |
+
|
83 |
+
if im2var[n_y][n_x] != -1:
|
84 |
+
A[e, im2var[n_y][n_x]] = -1
|
85 |
+
else:
|
86 |
+
b[e] += img_t[n_y][n_x]
|
87 |
+
e += 1
|
88 |
+
|
89 |
+
A = sp.sparse.csr_matrix(A)
|
90 |
+
v = sp.sparse.linalg.lsqr(A, b)[0]
|
91 |
+
|
92 |
+
img_t_out = img_t.copy()
|
93 |
+
|
94 |
+
for n in range(nnz):
|
95 |
+
y, x = ys[n], xs[n]
|
96 |
+
img_t_out[y][x] = v[im2var[y][x]]
|
97 |
+
|
98 |
+
return np.clip(img_t_out, 0, 1)
|
99 |
+
|
100 |
+
def _2d_gaussian(sigma):
|
101 |
+
ksize = np.int(np.ceil(sigma)*6+1)
|
102 |
+
gaussian_1d = cv2.getGaussianKernel(ksize, sigma)
|
103 |
+
return gaussian_1d * np.transpose(gaussian_1d)
|
104 |
+
|
105 |
+
def _low_pass_filter(img, sigma):
|
106 |
+
return cv2.filter2D(img, -1, _2d_gaussian(sigma))
|
107 |
+
|
108 |
+
def _high_pass_filter(img, sigma):
|
109 |
+
return img - _low_pass_filter(img, sigma)
|
110 |
+
|
111 |
+
def _gaus_pyramid(img, depth, sigma):
|
112 |
+
_im = img.copy()
|
113 |
+
pyramid = []
|
114 |
+
for d in range(depth-1):
|
115 |
+
_im = _low_pass_filter(_im.copy(), sigma)
|
116 |
+
pyramid.append(_im)
|
117 |
+
_im = cv2.pyrDown(_im)
|
118 |
+
return pyramid
|
119 |
+
|
120 |
+
def _lap_pyramid(img, depth, sigma):
|
121 |
+
_im = img.copy()
|
122 |
+
pyramid = []
|
123 |
+
for d in range(depth-1):
|
124 |
+
lap = _high_pass_filter(_im.copy(), sigma)
|
125 |
+
pyramid.append(lap)
|
126 |
+
_im = cv2.pyrDown(_im)
|
127 |
+
return pyramid
|
128 |
+
|
129 |
+
def _blend(img1, img2, mask):
|
130 |
+
return img1 * mask + img2 * (1.0 - mask)
|
131 |
+
|
132 |
+
def laplacian_blend(img1, img2, mask, depth=5, sigma=25):
|
133 |
+
mask_gaus_pyramid = _gaus_pyramid(mask, depth, sigma)
|
134 |
+
img1_lap_pyramid, img2_lap_pyramid = _lap_pyramid(img1, depth, sigma), _lap_pyramid(img2, depth, sigma)
|
135 |
+
|
136 |
+
blended = [_blend(obj, bg, mask) for obj, bg, mask in zip(img1_lap_pyramid, img2_lap_pyramid, mask_gaus_pyramid)][::-1]
|
137 |
+
|
138 |
+
h, w = blended[0].shape[:2]
|
139 |
+
|
140 |
+
img1 = cv2.resize(img1, (w, h))
|
141 |
+
img2 = cv2.resize(img2, (w, h))
|
142 |
+
mask = cv2.resize(mask, (w, h))
|
143 |
+
|
144 |
+
blanded_img = _blend(img1, img2, mask)
|
145 |
+
blanded_img = cv2.resize(blanded_img, blended[0].shape[:2])
|
146 |
+
|
147 |
+
imgs = []
|
148 |
+
for d in range(0, depth-1):
|
149 |
+
gaussian_img = _low_pass_filter(blanded_img.copy(), sigma)
|
150 |
+
reconstructed_img = cv2.add(blended[d], gaussian_img)
|
151 |
+
|
152 |
+
imgs.append(reconstructed_img)
|
153 |
+
blanded_img = cv2.pyrUp(reconstructed_img)
|
154 |
+
|
155 |
+
return np.clip(imgs[-1], 0, 1)
|
156 |
+
|
157 |
+
def load_example_images(bg_path, obj_path, mask_path):
|
158 |
+
bg_img = cv2.imread(bg_path)
|
159 |
+
bg_img = cv2.cvtColor(bg_img, cv2.COLOR_BGR2RGB)
|
160 |
+
|
161 |
+
obj_img = cv2.imread(obj_path)
|
162 |
+
obj_img = cv2.cvtColor(obj_img, cv2.COLOR_BGR2RGB)
|
163 |
+
|
164 |
+
mask_img = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
|
165 |
+
mask_img = np.where(mask_img > 127, 255, 0).astype(np.uint8)
|
166 |
+
|
167 |
+
return bg_img, obj_img, mask_img
|
168 |
+
|
169 |
+
# Modify the blend_images function to accept numpy arrays directly
|
170 |
+
def blend_images(bg_img, obj_img, mask_img, blend_method):
|
171 |
+
bg_img = get_image(bg_img)
|
172 |
+
obj_img = get_image(obj_img)
|
173 |
+
mask_img = get_image(mask_img, mask=True)
|
174 |
+
|
175 |
+
# Resize mask to match object image size
|
176 |
+
mask_img = cv2.resize(mask_img, (obj_img.shape[1], obj_img.shape[0]))
|
177 |
+
|
178 |
+
if blend_method == "Poisson":
|
179 |
+
blend_func = poisson_blend
|
180 |
+
elif blend_method == "Mixed Gradient":
|
181 |
+
blend_func = mixed_blend
|
182 |
+
else: # Laplacian
|
183 |
+
return laplacian_blend(obj_img, bg_img, np.stack((mask_img,)*3, axis=-1), 5, 25)
|
184 |
+
|
185 |
+
blend_img = np.zeros(bg_img.shape)
|
186 |
+
for b in range(3):
|
187 |
+
blend_img[:,:,b] = blend_func(obj_img[:,:,b], mask_img, bg_img[:,:,b].copy())
|
188 |
+
|
189 |
+
return (blend_img * 255).astype(np.uint8)
|
190 |
+
|
191 |
+
examples = [
|
192 |
+
["img1.jpg", "img2.jpg", "mask1.jpg", "Poisson"],
|
193 |
+
["img3.jpg", "img4.jpg", "mask2.jpg", "Mixed Gradient"],
|
194 |
+
["img6.jpg", "img9.jpg", "mask3.jpg", "Laplacian"]
|
195 |
+
]
|
196 |
+
|
197 |
+
iface = gr.Interface(
|
198 |
+
fn=blend_images,
|
199 |
+
inputs=[
|
200 |
+
gr.Image(label="Background Image", type="numpy"),
|
201 |
+
gr.Image(label="Object Image", type="numpy"),
|
202 |
+
gr.Image(label="Mask Image", type="numpy"),
|
203 |
+
gr.Radio(["Poisson", "Mixed Gradient", "Laplacian"], label="Blending Method")
|
204 |
+
],
|
205 |
+
outputs=gr.Image(label="Blended Image"),
|
206 |
+
title="Image Blending with Examples",
|
207 |
+
description="Choose from example images or upload your own to blend using different methods.",
|
208 |
+
examples=examples,
|
209 |
+
cache_examples=True
|
210 |
+
)
|
211 |
+
|
212 |
+
iface.launch()
|