max commited on
Commit
d6c4ad3
1 Parent(s): 61f59ec

initial commit

Browse files
Files changed (3) hide show
  1. app.py +136 -0
  2. packages.txt +1 -0
  3. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %%
2
+
3
+ import cv2
4
+ from sklearn.cluster import KMeans
5
+ from PIL import Image
6
+ import numpy as np
7
+ import gradio.components as gc
8
+ import gradio as gr
9
+
10
+
11
+ def pixart(
12
+ i,
13
+ block_size=4,
14
+ n_clusters=5,
15
+ hsv_weights=[0, 0, 1],
16
+ local_contrast_blur_radius=51, # has to be odd
17
+ upscale=True,
18
+ seed=None,
19
+ ):
20
+ w, h = i.size
21
+ dw = w//block_size
22
+ dh = h//block_size
23
+
24
+ # always resize with NEAREST to keep the original colors
25
+ i = i.resize((dw, dh), Image.Resampling.NEAREST)
26
+ ai = np.array(i)
27
+
28
+ if seed is None:
29
+ # seed = np.random.randint(0, 2**32 - 1)
30
+ seed = np.random.randint(0, 2**16 - 1)
31
+ km = KMeans(n_clusters=n_clusters, random_state=seed)
32
+
33
+ hsv = cv2.cvtColor(ai, cv2.COLOR_RGB2HSV)
34
+ bhsv = cv2.GaussianBlur(
35
+ hsv,
36
+ (local_contrast_blur_radius, local_contrast_blur_radius),
37
+ 0,
38
+ borderType=cv2.BORDER_REPLICATE
39
+ )
40
+ hsv32 = hsv.astype(np.float32)
41
+ km.fit(
42
+ hsv32.reshape(-1, hsv32.shape[-1]),
43
+ # (sharp-blurred) gives large values if a pixel stands out from its surroundings
44
+ # raise to the power of 4 to make the difference more pronounced.
45
+ # this preserves rare specks of color by increasing the probability of them getting their own cluster
46
+ sample_weight=(
47
+ np.linalg.norm((hsv32 - bhsv), axis=-1).reshape(-1)
48
+ ** 4
49
+ )
50
+ )
51
+ label_grid = km.labels_.reshape(hsv32.shape[:2])
52
+ centers = km.cluster_centers_ # hsv values
53
+
54
+ def pick_representative_pixel(cluster):
55
+ '''pick the representative pixel for a cluster'''
56
+ most_sat_color = (hsv[label_grid == cluster] @
57
+ np.array(hsv_weights)).argmax()
58
+ return hsv[label_grid == cluster][most_sat_color]
59
+ cluster_colors = np.array([
60
+ pick_representative_pixel(c)
61
+ for c in range(centers.shape[0])])
62
+
63
+ # assign each pixel the color of its cluster
64
+ ki = cluster_colors[label_grid]
65
+
66
+ rgb = cv2.cvtColor(ki.astype(np.uint8), cv2.COLOR_HSV2RGB)
67
+ i = Image.fromarray(rgb)
68
+ if upscale:
69
+ i = i.resize((w, h), Image.Resampling.NEAREST)
70
+ return i, seed
71
+
72
+
73
+ def query(
74
+ i: Image.Image,
75
+ block_size: str,
76
+ n_clusters, # =5,
77
+ hsv_weights, # ='0,0,1'
78
+ local_contrast_blur_radius, # =51 has to be odd
79
+ seed, # =42,
80
+ ):
81
+ bs = float(block_size)
82
+ w, h = i.size
83
+ if bs < 1:
84
+ blsz = int(bs * min(w, h))
85
+ else:
86
+ blsz = int(bs)
87
+
88
+ hw = [float(w) for w in hsv_weights.split(',')]
89
+
90
+ pxart, usedseed = pixart(
91
+ i,
92
+ block_size=blsz,
93
+ n_clusters=n_clusters,
94
+ hsv_weights=hw,
95
+ local_contrast_blur_radius=local_contrast_blur_radius,
96
+ upscale=True,
97
+ seed=int(seed) if seed != '' else None,
98
+ )
99
+ return pxart.convert('P', palette=Image.Palette.ADAPTIVE, colors=n_clusters), usedseed
100
+
101
+
102
+ # %%
103
+ searchimage = gc.Image(
104
+ # shape=(512, 512),
105
+ label="Search image", type='pil')
106
+ block_size = gc.Textbox(
107
+ "0.01",
108
+ label='Block Size ',
109
+ placeholder="e.g. 8 for 8 pixels. 0.01 for 1% of min(w,h) (<1 for percentages, >= 1 for pixels)")
110
+ palette_size = gc.Slider(
111
+ 1, 256, 32, step=1, label='Palette Size (Number of Colors)')
112
+ hsv_weights = gc.Textbox(
113
+ "0,0,1",
114
+ label='HSV Weights. Weights of the channels when selecting a "representative pixel"/centroid from a cluster of pixels',
115
+ placeholder='e.g. 0,0,1 to only consider the V channel (which seems to work well)')
116
+ lcbr = gc.Slider(
117
+ 3, 512, 51, step=2, label='Blur radius to calculate local contrast')
118
+
119
+ seed = gc.Textbox(
120
+ "",
121
+ label='Seed for the random number generator (empty to randomize)',
122
+ placeholder='e.g. 42')
123
+
124
+ outimage = gc.Image(shape=(224, 224), label="Output", type='pil')
125
+ seedout = gc.Textbox(label='used seed')
126
+
127
+
128
+ gr.Interface(
129
+ query,
130
+ [searchimage, block_size, palette_size, hsv_weights, lcbr, seed],
131
+ [outimage, seedout],
132
+ title="kmeans-Pixartifier",
133
+ description=f"Turns images into pixel art using kmeans clustering",
134
+ analytics_enabled=False,
135
+ allow_flagging='never',
136
+ ).launch()
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python3-opencv
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ opencv-python
2
+ scikit-learn
3
+