-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcartooner.py
147 lines (127 loc) · 4.33 KB
/
cartooner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python
import argparse
import time
import numpy as np
from collections import defaultdict
from scipy import stats
import cv2
def cartoonize(image):
"""
convert image into cartoon-like image
image: input PIL image
"""
output = np.array(image)
x, y, c = output.shape
# hists = []
for i in range(c):
output[:, :, i] = cv2.bilateralFilter(output[:, :, i], 5, 50, 50)
# hist, _ = np.histogram(output[:, :, i], bins=np.arange(256+1))
# hists.append(hist)
edge = cv2.Canny(output, 100, 200)
output = cv2.cvtColor(output, cv2.COLOR_RGB2HSV)
hists = []
#H
hist, _ = np.histogram(output[:, :, 0], bins=np.arange(180+1))
hists.append(hist)
#S
hist, _ = np.histogram(output[:, :, 1], bins=np.arange(256+1))
hists.append(hist)
#V
hist, _ = np.histogram(output[:, :, 2], bins=np.arange(256+1))
hists.append(hist)
C = []
for h in hists:
C.append(k_histogram(h))
print("centroids: {0}".format(C))
output = output.reshape((-1, c))
for i in range(c):
channel = output[:, i]
index = np.argmin(np.abs(channel[:, np.newaxis] - C[i]), axis=1)
output[:, i] = C[i][index]
output = output.reshape((x, y, c))
output = cv2.cvtColor(output, cv2.COLOR_HSV2RGB)
contours, _ = cv2.findContours(edge,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_NONE)
# for i in range(len(contours)):
# tmp = contours[i]
# contours[i] = cv2.approxPolyDP(tmp, 2, False)
cv2.drawContours(output, contours, -1, 0, thickness=1)
return output
def update_C(C, hist):
"""
update centroids until they don't change
"""
while True:
groups = defaultdict(list)
#assign pixel values
for i in range(len(hist)):
if hist[i] == 0:
continue
d = np.abs(C-i)
index = np.argmin(d)
groups[index].append(i)
new_C = np.array(C)
for i, indice in groups.items():
if np.sum(hist[indice]) == 0:
continue
new_C[i] = int(np.sum(indice*hist[indice])/np.sum(hist[indice]))
if np.sum(new_C-C) == 0:
break
C = new_C
return C, groups
def k_histogram(hist):
"""
choose the best K for k-means and get the centroids
"""
alpha = 0.001 # p-value threshold for normaltest
N = 80 # minimun group size for normaltest
C = np.array([128])
while True:
C, groups = update_C(C, hist)
#start increase K if possible
new_C = set() # use set to avoid same value when seperating centroid
for i, indice in groups.items():
#if there are not enough values in the group, do not seperate
if len(indice) < N:
new_C.add(C[i])
continue
# judge whether we should seperate the centroid
# by testing if the values of the group is under a
# normal distribution
z, pval = stats.normaltest(hist[indice])
if pval < alpha:
#not a normal dist, seperate
left = 0 if i == 0 else C[i-1]
right = len(hist)-1 if i == len(C)-1 else C[i+1]
delta = right-left
if delta >= 3:
c1 = (C[i]+left)/2
c2 = (C[i]+right)/2
new_C.add(c1)
new_C.add(c2)
else:
# though it is not a normal dist, we have no
# extra space to seperate
new_C.add(C[i])
else:
# normal dist, no need to seperate
new_C.add(C[i])
if len(new_C) == len(C):
break
else:
C = np.array(sorted(new_C))
return C
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('input', help='input image')
parser.add_argument('output', help='output cartoonized image')
args = parser.parse_args()
# image = Image.open(args.input)
image = cv2.imread(args.input)
start_time = time.time()
output = cartoonize(image)
end_time = time.time()
t = end_time-start_time
print('time: {0}s'.format(t))
cv2.imwrite(args.output, output)