kargaranamir commited on
Commit
dc4014d
1 Parent(s): b18e067

add color harmonization

Browse files
Files changed (4) hide show
  1. app.py +82 -0
  2. packages.txt +6 -0
  3. requirements.txt +6 -0
  4. utils.py +258 -0
app.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ from utils import *
4
+
5
+ torch.hub.download_url_to_file(
6
+ 'https://github.com/aalto-ui/aim/raw/aim2/backend/data/tests/input_values/wikipedia.org_website.png',
7
+ 'wikipedia.org_website.png')
8
+ torch.hub.download_url_to_file(
9
+ 'https://github.com/aalto-ui/aim/raw/aim2/backend/data/tests/input_values/aalto.fi_website.png',
10
+ 'aalto.fi_website.png')
11
+
12
+
13
+ def inference(img, template, angel):
14
+ color_image = cv2.imread(img.name, cv2.IMREAD_COLOR)
15
+ height, width, _ = color_image.shape
16
+
17
+ # Resize if it is bigeer than 960 * 800
18
+ if width > height:
19
+ if width > 960: # 3/4 * 1280
20
+ coef_div = width / 960.0
21
+ color_image = cv2.resize(color_image, dsize=(int(width / coef_div), int(height / coef_div)),
22
+ interpolation=cv2.INTER_CUBIC)
23
+ else:
24
+ if height > 800: # 800
25
+ coef_div = height / 800.0
26
+ color_image = cv2.resize(color_image, dsize=(int(width / coef_div), int(height / coef_div)),
27
+ interpolation=cv2.INTER_CUBIC)
28
+
29
+ HSV_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2HSV)
30
+ selected_harmomic_scheme = HarmonicScheme(str(template), int(angel))
31
+ new_HSV_image = best_harmomic_scheme.hue_shifted(HSV_image, num_superpixels=-1)
32
+
33
+ # Compute shifted histogram
34
+ histo_1 = count_hue_histogram(HSV_image)
35
+ histo_2 = count_hue_histogram(new_HSV_image)
36
+
37
+ # Create Hue Plots
38
+ fig1 = plothis(histo_1, best_harmomic_scheme, "Source Hue")
39
+ fig_1_cv = get_img_from_fig(fig1)
40
+ fig2 = plothis(histo_2, best_harmomic_scheme, "Target Hue")
41
+ fig_2_cv = get_img_from_fig(fig2)
42
+
43
+ # Stack Hue Plots
44
+ vis = np.concatenate((fig_1_cv, fig_2_cv), axis=0)
45
+ # Convert HSV to BGR
46
+ result_image = cv2.cvtColor(new_HSV_image, cv2.COLOR_HSV2BGR)
47
+
48
+ # Final output
49
+ canvas = np.full((800, 960, 3), (255, 255, 255), dtype=np.uint8)
50
+ # compute center offset
51
+ x_center = (960 - width) // 2
52
+ y_center = (800 - height) // 2
53
+ # copy img image into center of result image
54
+ canvas[y_center:y_center + height, x_center:x_center + width] = result_image
55
+
56
+ # Combine
57
+ output = np.concatenate((vis, canvas), axis=1)
58
+ cv2.imwrite('output.png', output)
59
+
60
+ return ['output.png']
61
+
62
+
63
+ title = 'Color Harmonization'
64
+ description = 'Compute Color Harmonization with Different Templates'
65
+ article = "<p style='text-align: center'></p>"
66
+ examples = [['wikipedia.org_website.png'], ['aalto.fi_website.png']]
67
+ css = ".output_image, .input_image {height: 40rem !important; width: 100% !important;}"
68
+
69
+ gr.Interface(
70
+ inference,
71
+ [gr.inputs.Image(type='file', label='Input'),
72
+ gr.inputs.Dropdown(["X", "Y", "T", "I", "mirror_L", "L", "V", "i"],
73
+ default="X",
74
+ label="Template"),
75
+ gr.inputs.Slider(0, 359, label="Angle")],
76
+ [gr.outputs.Image(type='file', label='Color Harmonization of Output Image')],
77
+ title=title,
78
+ description=description,
79
+ article=article,
80
+ examples=examples,
81
+ css=css,
82
+ ).launch(debug=True, enable_queue=True)
packages.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ libgl1
2
+ libglib2.0-0
3
+ libgl1-mesa-glx
4
+ ffmpeg
5
+ libsm6
6
+ libxext6
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ opencv-python-headless
2
+ torch
3
+ gradio
4
+ opencv-python==4.1.2
5
+ numpy==1.21.6
6
+ matplotlib==3.2.2
utils.py ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Color Harmonization utility functions.
6
+ Some Codes are imported and adopted from https://github.com/tartarskunk/ColorHarmonization
7
+ """
8
+
9
+ # Import Libraries
10
+ import cv2
11
+ import numpy as np
12
+ import matplotlib.pyplot as plt
13
+ import io
14
+
15
+ # Constants
16
+ HueTemplates = {
17
+ "i": [(0.00, 0.05)],
18
+ "V": [(0.00, 0.26)],
19
+ "L": [(0.00, 0.05), (0.25, 0.22)],
20
+ "mirror_L": [(0.00, 0.05), (-0.25, 0.22)],
21
+ "I": [(0.00, 0.05), (0.50, 0.05)],
22
+ "T": [(0.25, 0.50)],
23
+ "Y": [(0.00, 0.26), (0.50, 0.05)],
24
+ "X": [(0.00, 0.26), (0.50, 0.26)],
25
+ }
26
+ template_types = list(HueTemplates.keys())
27
+ M = len(template_types)
28
+ A = 360
29
+
30
+
31
+ def deg_distance(a, b):
32
+ d1 = np.abs(a - b)
33
+ d2 = np.abs(360 - d1)
34
+ d = np.minimum(d1, d2)
35
+ return d
36
+
37
+
38
+ def normalized_gaussian(X, mu, S):
39
+ X = np.asarray(X).astype(np.float64)
40
+ S = np.asarray(S).astype(np.float64)
41
+ D = np.deg2rad(X - mu)
42
+ S = np.deg2rad(S)
43
+ D2 = np.multiply(D, D)
44
+ S2 = np.multiply(S, S)
45
+ return np.exp(-D2 / (2 * S2))
46
+
47
+
48
+ class HueSector:
49
+
50
+ def __init__(self, center, width):
51
+ # In Degree [0,2 pi)
52
+ self.center = center
53
+ self.width = width
54
+ self.border = [(self.center - self.width / 2), (self.center + self.width / 2)]
55
+
56
+ def is_in_sector(self, H):
57
+ # True/False matrix if hue resides in the sector
58
+ return deg_distance(H, self.center) < self.width / 2
59
+
60
+ def distance_to_border(self, H):
61
+ H_1 = deg_distance(H, self.border[0])
62
+ H_2 = deg_distance(H, self.border[1])
63
+ H_dist2bdr = np.minimum(H_1, H_2)
64
+ return H_dist2bdr
65
+
66
+ def closest_border(self, H):
67
+ H_1 = deg_distance(H, self.border[0])
68
+ H_2 = deg_distance(H, self.border[1])
69
+ H_cls_bdr = np.argmin((H_1, H_2), axis=0)
70
+ H_cls_bdr = 2 * (H_cls_bdr - 0.5)
71
+ return H_cls_bdr
72
+
73
+ def distance_to_center(self, H):
74
+ H_dist2ctr = deg_distance(H, self.center)
75
+ return H_dist2ctr
76
+
77
+
78
+ class HarmonicScheme:
79
+
80
+ def __init__(self, m, alpha):
81
+ self.m = m
82
+ self.alpha = alpha
83
+ self.reset_sectors()
84
+
85
+ def reset_sectors(self):
86
+ self.sectors = []
87
+ for t in HueTemplates[self.m]:
88
+ center = t[0] * 360 + self.alpha
89
+ width = t[1] * 360
90
+ sector = HueSector(center, width)
91
+ self.sectors.append(sector)
92
+
93
+ def harmony_score(self, X):
94
+ # Opencv store H as [0, 180) --> [0, 360)
95
+ H = X[:, :, 0].astype(np.int32) * 2
96
+ # Opencv store S as [0, 255] --> [0, 1]
97
+ S = X[:, :, 1].astype(np.float32) / 255.0
98
+
99
+ H_dis = self.hue_distance(H)
100
+ H_dis = np.deg2rad(H_dis)
101
+ return np.sum(np.multiply(H_dis, S))
102
+
103
+ def hue_distance(self, H):
104
+ H_dis = []
105
+ for i in range(len(self.sectors)):
106
+ sector = self.sectors[i]
107
+ H_dis.append(sector.distance_to_border(H))
108
+ H_dis[i][sector.is_in_sector(H)] = 0
109
+ H_dis = np.asarray(H_dis)
110
+ H_dis = H_dis.min(axis=0)
111
+ return H_dis
112
+
113
+ def hue_shifted(self, X, num_superpixels=-1):
114
+ Y = X.copy()
115
+ H = X[:, :, 0].astype(np.int32) * 2
116
+ S = X[:, :, 1].astype(np.float32) / 255.0
117
+
118
+ H_d2b = [sector.distance_to_border(H) for sector in self.sectors]
119
+ H_d2b = np.asarray(H_d2b)
120
+
121
+ H_cls = np.argmin(H_d2b, axis=0)
122
+ if num_superpixels != -1:
123
+ SEEDS = cv2.ximgproc.createSuperpixelSEEDS(X.shape[1], X.shape[0], X.shape[2], num_superpixels, 10)
124
+ SEEDS.iterate(X, 4)
125
+
126
+ V = np.zeros(H.shape).reshape(-1)
127
+ N = V.shape[0]
128
+
129
+ H_ctr = np.zeros((H.shape))
130
+ grid_num = SEEDS.getNumberOfSuperpixels()
131
+ labels = SEEDS.getLabels()
132
+ for i in range(grid_num):
133
+
134
+ P = [[], []]
135
+ s = np.average(H_cls[labels == i])
136
+ if s > 0.5:
137
+ s = 1
138
+ else:
139
+ s = 0
140
+ H_cls[labels == i] = s
141
+
142
+ H_ctr = np.zeros((H.shape))
143
+ H_wid = np.zeros((H.shape))
144
+ H_d2c = np.zeros((H.shape))
145
+ H_dir = np.zeros((H.shape))
146
+
147
+ for i in range(len(self.sectors)):
148
+ sector = self.sectors[i]
149
+ mask = (H_cls == i)
150
+ H_ctr[mask] = sector.center
151
+ H_wid[mask] = sector.width
152
+ H_dir += sector.closest_border(H) * mask
153
+ H_dist2ctr = sector.distance_to_center(H)
154
+ H_d2c += H_dist2ctr * mask
155
+
156
+ H_sgm = H_wid / 2
157
+ H_gau = normalized_gaussian(H_d2c, 0, H_sgm)
158
+ H_tmp = np.multiply(H_wid / 2, 1 - H_gau)
159
+ H_shf = np.multiply(H_dir, H_tmp)
160
+ H_new = (H_ctr + H_shf).astype(np.int32)
161
+
162
+ for i in range(len(self.sectors)):
163
+ sector = self.sectors[i]
164
+ mask = sector.is_in_sector(H)
165
+ np.copyto(H_new, H, where=sector.is_in_sector(H))
166
+
167
+ H_new = np.remainder(H_new, 360)
168
+ H_new = (H_new / 2).astype(np.uint8)
169
+ Y[:, :, 0] = H_new
170
+ return Y
171
+
172
+
173
+ def count_hue_histogram(X):
174
+ N = 360
175
+ H = X[:, :, 0].astype(np.int32) * 2
176
+ S = X[:, :, 1].astype(np.float64) / 255.0
177
+ H_flat = H.flatten()
178
+ S_flat = S.flatten()
179
+
180
+ histo = np.zeros(N)
181
+ for i in range(len(H_flat)):
182
+ histo[H_flat[i]] += S_flat[i]
183
+ return histo
184
+
185
+
186
+ def plothis(hue_histo, harmonic_scheme, caption: str):
187
+ N = 360
188
+
189
+ # Compute pie slices
190
+ theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
191
+ width = np.pi / 180
192
+
193
+ # Compute colors, RGB values for the hue
194
+ hue_colors = np.zeros((N, 4))
195
+ for i in range(hue_colors.shape[0]):
196
+ color_HSV = np.zeros((1, 1, 3), dtype=np.uint8)
197
+ color_HSV[0, 0, :] = [int(i / 2), 255, 255]
198
+ color_BGR = cv2.cvtColor(color_HSV, cv2.COLOR_HSV2BGR)
199
+ B = int(color_BGR[0, 0, 0]) / 255.0
200
+ G = int(color_BGR[0, 0, 1]) / 255.0
201
+ R = int(color_BGR[0, 0, 2]) / 255.0
202
+ hue_colors[i] = (R, G, B, 1.0)
203
+
204
+ # Compute colors, for the shadow
205
+ shadow_colors = np.zeros((N, 4))
206
+ for i in range(shadow_colors.shape[0]):
207
+ shadow_colors[i] = (0.0, 0.0, 0.0, 1.0)
208
+
209
+ # Create hue, guidline and shadow arrays
210
+ hue_histo = hue_histo.astype(float)
211
+
212
+ hue_histo_msx = float(np.max(hue_histo))
213
+ if hue_histo_msx != 0.0:
214
+ hue_histo /= np.max(hue_histo)
215
+ guide_histo = np.array([0.05] * N)
216
+ shadow_histo = np.array([0.0] * N)
217
+
218
+ # Compute angels of shadow, template types
219
+ for sector in harmonic_scheme.sectors:
220
+ sector_center = sector.center
221
+ sector_width = sector.width
222
+ end = int((sector_center + sector_width / 2) % 360)
223
+ start = int((sector_center - sector_width / 2) % 360)
224
+
225
+ if start < end:
226
+ shadow_histo[start: end] = 1.0
227
+ else:
228
+ shadow_histo[start: 360] = 1.0
229
+ shadow_histo[0: end] = 1.0
230
+
231
+ # Plot, 1280 * 800
232
+ fig = plt.figure(figsize=(3.2, 4))
233
+ ax = fig.add_subplot(111, projection='polar')
234
+ # add hue histogram
235
+ ax.bar(theta, hue_histo, width=width, bottom=0.0, color=hue_colors, alpha=1.0)
236
+ # add guidline
237
+ ax.bar(theta, guide_histo, width=width, bottom=1.0, color=hue_colors, alpha=1.0)
238
+ # add shadow angels for the template types
239
+ ax.bar(theta, shadow_histo, width=width, bottom=0.0, color=shadow_colors, alpha=0.1)
240
+ ax.set_title(caption, pad=15)
241
+
242
+ plt.close()
243
+
244
+ return fig
245
+
246
+
247
+ # https://stackoverflow.com/questions/7821518/matplotlib-save-plot-to-numpy-array
248
+ def get_img_from_fig(fig, dpi=100):
249
+ """
250
+ a function which returns an image as numpy array from figure
251
+ """
252
+ buf = io.BytesIO()
253
+ fig.savefig(buf, format="png", dpi=dpi)
254
+ buf.seek(0)
255
+ img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)
256
+ buf.close()
257
+ img = cv2.imdecode(img_arr, 1)
258
+ return img