-
Notifications
You must be signed in to change notification settings - Fork 0
/
vectorize.py
executable file
·343 lines (233 loc) · 9.74 KB
/
vectorize.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#!/usr/bin/env python
#
# this is ugly, experimental, slow, unoptimized code.
# its purpose is to verify my (propably) stupid idea
# of an "image to hatching pen plotter drawing".
#
# for now the script finds edges and fills areas in between
# with colors
# edge detection works as expected, but drawn edges
# (black lines...) are not handled in a special way
# = handled like a normal area/fill = results in double
# lines detected for comic like input drawings
#
# motion vector recovery works ok-ish.
#
# this script now generates lots of data from an image file
# next, yet unimplemented, step is to combine all this data
# and generate polygon lines
#
# another, yet unimplemented, part is to write all the polygons
# out in HPGL form.
#
#
# see main() function for actual high level description of what is going on
#
# we start with a few simple image manipulation functions:
#
import sys
from lib_console import *
from lib_imagefile import *
from lib_colors import *
from lib_image_filters import *
from lib_paths import *
from lib_motionvectors import *
from lib_hpgl import *
def facewalk(img_in, mask, motionvectors):
'''trace hatching paths'''
mask_clone = pygame.Surface(mask.get_size())
mask_clone.blit(mask,(0,0))
radius = 5
paths = []
width, height = mask_clone.get_size()
while True:
path = []
position = find_pixel_with_color(mask_clone,(255,255,255))
if not position:
break
path.append(position)
mask_clone.set_at(position,(0,0,0)) # deactivate pixel
# now, find next pixel
while True:
vector = interpolate_motionvectors(motionvectors,position)
angle = vector[1]
#print "!!!",vector
dp = [math.cos(angle*2*math.pi/360)*radius,math.sin(angle*2*math.pi/360)*radius]
position = [position[0]+int(dp[0]),position[1]+int(dp[1])]
if position[0]<0 or position[0]>width-1 or position[1]<0 or position[1]>height-1:
break
if mask_clone.get_at(position) == (0,0,0):
break
path.append(position)
mask_clone.set_at(position,(0,0,0))
pygame.draw.circle(mask_clone, (0,0,0), position, radius-1, 0)
paths.append(path)
return paths
def main(input_file,mode=""):
'''main routine'''
if len(sys.argv)>=2: # 1 parameters
pygame.init()
# the idea is as follows:
# 1. find/mark edges, because edges mark areas
img_in = image_load(input_file)
display = image_gui(img_in.get_size())
image_show(display, img_in, False)
#img_blurx = lazyblur(img_in,3)
#image_show(display,img_blurx, True)
#time.sleep(5)
# a) first we run an median filter to get rid of noise but keep edges
# as the median filter already gets a list of all pixels around the analyzed coordinate
# it got an additional part to calculate the contrast.
if mode != "bw":
img_median, img_border = median(img_in,3,"c",3)
image_show(display, img_median, True)
image_show(display, img_border, True)
image_save(img_border,fn_comb(input_file,"borderm"))
image_save(img_median,fn_comb(input_file,"median"))
img_in = img_median
else:
# just create inverted image...
img_white = image_create(img_in.get_size(),(255,255,255))
img_border = addmul(img_in,img_white,1.0,-1.0)
image_show(display, img_border, True)
#img_in = img_median
img_blend = img_border
# e) create blured image (average of local area)
img_blur = img_blend
for i in range(0,3):
img_blur = lazyblur(img_blur, 3)
image_show(display, img_blur, False)
# f) unblured - blured edge image = better image for threshold usage (adapts to local variations)
img_blurdif = addmul(img_blend, img_blur, -1)
image_show(display, img_blurdif, True)
# j) walk the line
edgepaths = edgewalk(img_blurdif)
img_edgepath = image_render_paths(edgepaths,img_in.get_size())
image_show(display, img_edgepath, True)
image_save(img_edgepath,fn_comb(sys.argv[1],"epath"))
c = hpgl_usepen(1,(0,0,0))
c+= hpgl_frompaths(edgepaths)
hpgl_tofile(c, fn_comb(sys.argv[1],"epath","hpgl"))
# g) bolden edges
img_bold = bolden(img_blurdif,1)
image_show(display, img_bold, True)
# h) convert to black and white via limit
img_bnw = blacknwhite(img_bold,12)
image_show(display, img_bnw, True)
image_show(display, img_bnw, True)
# i) isles smaller than limit get eliminated
while True:
position = find_pixel_with_color(img_bnw,(255,255,255))
if not position:
break
pixelcount = floodfill(img_bnw, position, (128,128,128))
if pixelcount < 100:
print "isle at %s, %i pixels below limit"%(str(position), pixelcount)
floodfill(img_bnw, position, (0,0,0)) # eliminate it
img_bnw = blacknwhite(img_bnw,4)
# 2. for the room between edges: flood fill until no space left
facecount = 0
while True:
position = find_pixel_with_color(img_bnw,(0,0,0))
if not position:
break
#print position
pixelcount = floodfill(img_bnw, position, id_to_color(facecount))
if pixelcount < 25:
print "isle at %s, %i pixels below limit"%(str(position), pixelcount)
floodfill(img_bnw, position, (255,255,255))
else:
facecount+=1
image_show(display, img_bnw, False)
print "filled %i faces"%facecount
# 3. with each flood fill a seperate area/mask is defined
masks = []
masks_drawtmp = pygame.Surface(img_bnw.get_size())
for i in range(0,facecount):
masks.append(bolden( mask(img_bnw,id_to_color(i)) ,2))
masks_drawtmp = blend(masks_drawtmp, masks[i])
image_show(display, masks_drawtmp, False)
# 4. get average brightness from this area
masked_originals = []
masked_originals_drawtmp = pygame.Surface(img_bnw.get_size())
avgcolors = []
for i in range(0,facecount):
avgcolor = get_avg_color(img_in, masks[i])
avgcolors.append(avgcolor)
print avgcolor
masked_originals.append( multiply( image_create(img_bnw.get_size(),avgcolor), masks[i]) )
#masked_originals.append(multiply(masks[i],img_in))
masked_originals_drawtmp = blend(masked_originals_drawtmp, masked_originals[i])
image_show(display, masked_originals_drawtmp, False)
image_save(masked_originals_drawtmp,fn_comb(sys.argv[1],"tmp"))
# 5. motion vector find
motionvector_drawtmp = pygame.Surface(img_bnw.get_size())
motionvectorss = []
for i in range(0,facecount):
print "motion vector face %i"%i
motionvectors = motionsfind(img_in, bolden(masks[i],5),10) # 10px radius
motionvectorss.append(motionvectors)
cormax = 0
corvar_max = 0
for vector in motionvectors:
pos,ang,ofs,cor,corvar = vector
# correlation: smaller = smaller difference in picture comparison 0 = identical, which is good
# corvar: correlation variance, bigger = better because we dont just probe signle colored surface...
#
# correlation needs normalisation
if cormax<cor:
cormax=cor
if corvar_max<corvar:
corvar_max=corvar
for vector in motionvectors:
pos,ang,ofs,cor,corvar = vector
# brightness of contrast image correlates to "roughness" at this location
#roughness = float(255-max(img_blur.get_at(pos)[:2]))/255
rel_cor = float(cor)/float(cormax)
rel_corvar = float(corvar)/float(corvar_max)
ofs=ofs*(1-rel_cor) * rel_corvar
vector.append(ofs)
# ofs+=1
if ofs>0:
endpos = 10*math.cos(ang*2*math.pi/360)*ofs+pos[0],10*math.sin(ang*2*math.pi/360)*ofs+pos[1]
pygame.draw.line(motionvector_drawtmp, avgcolors[i], pos, endpos, 1)
image_show(display, motionvector_drawtmp, True)
image_save(motionvector_drawtmp,fn_comb(sys.argv[1],"vector"))
combv = []
for motionvectors in motionvectorss: # combine all gathered motion vectors
combv+=motionvectors
#print combv
motionvector_r = motionvector_rainbow(combv,img_bnw.get_size()) # calculate rainbow image from motion vectors
image_show(display, motionvector_r, True)
image_save(motionvector_r,fn_comb(sys.argv[1],"vectom"))
# 6. generate strokes/hatching for each area. it is not necessary to know the area outline as polygon, just check the individual pixels
# buggy!
img_strokepath = pygame.Surface(img_in.get_size())
strokepathss = []
#if False:
for i in range(0,len(masks)):
strokepaths = facewalk(img_in, masks[i], motionvectorss[i])
for polygon in strokepaths:
lastpoint = False
if len(polygon)>1:
for point in polygon:
if not lastpoint:
lastpoint = point
#pygame.draw.circle(img_strokepath, (0,255,0),point, 3)
pygame.draw.line(img_strokepath, avgcolors[i], lastpoint, point, 1)
lastpoint = point
strokepathss.append(strokepaths)
image_show(display, img_strokepath, True)
image_save(img_strokepath,fn_comb(sys.argv[1],"fpath"))
pygame.display.flip()
time.sleep(10)
if __name__ == "__main__":
import pygame
import sys
import time
import math
import random
if len(sys.argv) == 2:
main(sys.argv[1])
if len(sys.argv) == 3:
main(sys.argv[1],sys.argv[2])