-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathseamcarving.py
232 lines (178 loc) · 6.05 KB
/
seamcarving.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
import sys
print "Script running on Python", sys.version
print ""
import numpy
from scipy import misc
# -----
def load_image(filename):
return misc.imread(filename)
def save_image(filename, img):
return misc.imsave(filename, img)
def flip_image(img):
return numpy.transpose(img)
def convert_image_to_grayscale(img):
return numpy.dot(img[...,:3], [0.299, 0.587, 0.114])
# -----
# not used because "that's too easy"
def calc_energy_function_numpy(img):
dx, dy = numpy.gradient(img)
energy = abs(dx) + abs(dy)
return energy
def calc_energy_function(img):
energy = numpy.zeros(shape=img.shape)
height, width = energy.shape
for r in range(height):
for c in range(width):
if c==0:
dx = img[r][c+1] - img[r][c]
elif c==width-1:
dx = img[r][c] - img[r][c-1]
else:
dx = img[r][c+1] - img[r][c-1]
if r==0:
dy = img[r+1][c] - img[r][c]
elif r==height-1:
dy = img[r][c] - img[r-1][c]
else:
dy = img[r+1][c] - img[r-1][c]
energy[r][c] = abs(dx) + abs(dy)
return energy
def calc_vertical_seam(energy):
# == INIT ==
height, width = energy.shape
# intialize working array with zeros
cost = numpy.zeros(shape=energy.shape)
# initialize first row of cost with values directly from energy function
for c in range(width):
cost[0][c] = energy[0][c]
# == CALCULATE COST ==
# for all other rows (all rows but the first we just initialized)
for r in range(1, height):
# edge case: first column is calculated from first and second columns only
cost[r][0] = energy[r][0] + min(cost[r-1][0], cost[r-1][1])
# general case: non-edge columns
for c in range(1, width-1):
# calculate from current column and the ones left and right of it
cost[r][c] = energy[r][c] + min(cost[r-1][c-1], cost[r-1][c], cost[r-1][c+1])
# edge case: last column is calculated from last and second-to-last columns only
cost[r][width-1] = energy[r][width-1] + min(cost[r-1][width-2], cost[r-1][width-1])
# DEBUG: export costs array
# numpy.savetxt('cost.txt', cost, fmt='%4.0f')
# == TRACE SEAM ==
# find smallest value in the last row
lowest_value = 999999999
lowest_index = 0
for c in range(width):
if cost[height-1][c] < lowest_value:
lowest_value = cost[height-1][c]
lowest_index = c
# DEBUG: show result
# print "lowest value: ", lowest_value, " @ index ", lowest_index
# init seam object
seam = [(height-1, lowest_index)]
# find best path in the other rows, one row up at a time
for r in range(height-2, -1, -1): # first -1: go until 0; second -1: decrease index in each iteration
# edge case: on the very left, only the first and second column are considered
if lowest_index == 0:
if cost[r][lowest_index+1] < cost[r][lowest_index]:
lowest_index += 1
#else lowest_index unchanged
# edge case: on the very right, only the last and second-to-last columns are considered
elif lowest_index == width-1:
if cost[r][lowest_index-1] < cost[r][lowest_index]:
lowest_index -= 1
#else lowest index unchanged
# general case: consider the current column and the ones left and right of it
else:
candidates = [cost[r][lowest_index-1], cost[r][lowest_index], cost[r][lowest_index+1]]
if candidates[0] < candidates[1] and candidates[0] < candidates[2]:
lowest_index -= 1
elif candidates[2] < candidates[0] and candidates[2] < candidates[1]:
lowest_index += 1
#else lowest_index unchanged
# add found point to seam object
seam.append((r, lowest_index))
# == DONE ==
return seam
def mark_vertical_seam(img, seam):
# set color of each pixel that is part of the seam to white
for p in seam:
img[p[0]][p[1]] = 255
return img
def remove_vertical_seam(img, seam):
# init
height, width = img.shape
imgsmall = numpy.ndarray(shape=(height, width-1))
# go through all rows
for r in range(height):
shifting = False
for c in range(width-1):
# if the current pixel is the current row's seam pixel (there's always exactly 1 in each row)...
if (r,c) in seam:
# ...remember that
shifting = True
# if we're past the seam pixel, copy the pixel from 1 step further right
if shifting:
imgsmall[r][c] = img[r][c+1]
# otherwise just copy from the very same location
else:
imgsmall[r][c] = img[r][c]
return imgsmall
def remove_vertical_seams(img, count, is_fake_horizontal=False):
target_width = img.shape[1] - count
while img.shape[1] > target_width:
if is_fake_horizontal:
print "Reducing to ", img.shape[0], " x ", img.shape[1]-1
else:
print "Reducing to ", img.shape[1]-1, " x ", img.shape[0]
energy = calc_energy_function(img)
seam = calc_vertical_seam(energy)
# DEBUG: show seam in image
# img = mark_vertical_seam(img, seam)
# save_image('seam.png', img)
img = remove_vertical_seam(img, seam)
return img
def remove_horizontal_seams(img, count):
img = flip_image(img)
img = remove_vertical_seams(img, count, True)
img = flip_image(img)
return img
def remove_seams(img, k, l):
height, width = img.shape
target_width = width - l
target_height = height - k
print "Image is ", width, " x ", height
print "Image will be ", target_width, " x ", target_height
print ""
print "Reducing width"
img = remove_vertical_seams(img, l)
print ""
print "Reducing height"
img = remove_horizontal_seams(img, k)
print ""
return img
def main(argv):
"""Takes four arguments:
1. filename of the image to be seam carved
2. number k of horizontal seams to be removed
3. number l of vertical seams to be removed
4. filename the seam carved image should be written to
For example:
tower.png 6 94 tower-seamcarved.png
"""
img = load_image(argv[1])
print "Image ", argv[1], " loaded"
img = convert_image_to_grayscale(img)
print "Converted to grayscale"
print ""
img = remove_seams(img, int(argv[2]), int(argv[3]))
print "Removed ", argv[2], " horizontal and ", argv[3], " vertical seams"
save_image(argv[4], img)
print "Saved image as ", argv[4]
print ""
print "DONE"
print ""
if __name__ == '__main__':
# DEBUG: always use test image and settings
# argv = ["seamcarving.py", "tower.png", 6, 94, "tower-seamcarved.png"]
main(sys.argv)