-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
263 lines (215 loc) · 12.7 KB
/
main.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
#Authored by: Pranav Subramanian and Will Seiple
#following: https://julie-jiang.github.io/image-segmentation/
#per https://brilliant.org/wiki/edmonds-karp-algorithm/, decided to use an adjacency matrix
from ui import pickImage
from graph import *
from PIL import Image
import numpy as np
import math
import sys
def penalty(u, v, sigma):
''' Computes boundary penalty given two Vertex objects
Input: u - a Vertex object
v - a Vertex object
sigma - a parameter to adjust the output of this function to something "workable", determined to be about 100000
Output: boundary penalty - int
'''
return int(100*math.exp((-(u.get_data()-v.get_data())**2)/(2*sigma**2))) #this boundary penalty was from: https://julie-jiang.github.io/image-segmentation/
def intensity(rgb):
''' Gets the intensity of an rgb array
Input: rgb - an array of 3 rgb values (i.e. [255, 255, 255])
Output: intensity - a 1D value corresponding to the greyscale value of the rgb array
'''
return 0.2126 * rgb[0] ** 2.2 + 0.7152 * rgb[1] ** 2.2 + 0.0722 * rgb[2] ** 2.2 #this was from: https://stackoverflow.com/questions/687261/converting-rgb-to-grayscale-intensity
def image_to_array(filename):
''' Converts image to a 2D list of RGB values (sub-arrays)
Input: filename - an image file name or path
Ouput: array - a 2D list representation of the image, with each element as an rgb array of 3 values
'''
im = Image.open(filename, 'r')
return np.asarray(im)
def add_vertices(graph, image_array):
''' Adds vertices from an array of image to our implementation of the graph
Input: graph - an instance of the Graph class
image_array - an array of image values
'''
im = image_array
width, height = im.shape[1], im.shape[0]
for row in range(0,height):
for col in range(0,width):
v = Vertex((row,col), intensity(im[row][col])) #makes a vertex out of each pixel, with its name set to a tuple of its coordinates, and its data set to its intensity
graph.add_vertex(v)
def add_edges(graph, image_array):
''' Adds the edges from an array of our image to our implementation of the graph
Input: graph - an instance of the Graph class
input_array - an array of image values
'''
im = image_array
width, height = im.shape[1]-1, im.shape[0]-1
for vertex_pos in graph.vertices.keys(): #goes through all vertices, and adds edges linking each Vertex representing a pixel to its four direct neighbors, with the edge weight set to the boundary penalty function between the two's intensities
if vertex_pos[0] < height:
graph.add_edge(vertex_pos, (vertex_pos[0]+1, vertex_pos[1]), penalty(graph.vertices[vertex_pos],graph.vertices[(vertex_pos[0]+1, vertex_pos[1])], 100000))
if vertex_pos[1] < width:
graph.add_edge(vertex_pos, (vertex_pos[0], vertex_pos[1]+1), penalty(graph.vertices[vertex_pos],graph.vertices[(vertex_pos[0], vertex_pos[1]+1)], 100000))
if vertex_pos[0] > 0:
graph.add_edge(vertex_pos, (vertex_pos[0]-1, vertex_pos[1]), penalty(graph.vertices[vertex_pos],graph.vertices[(vertex_pos[0]-1, vertex_pos[1])], 100000))
if vertex_pos[1] > 0:
graph.add_edge(vertex_pos, (vertex_pos[0], vertex_pos[1]-1), penalty(graph.vertices[vertex_pos],graph.vertices[(vertex_pos[0], vertex_pos[1]-1)], 100000))
def add_source_sink(graph, source, sink):
''' Adds the sources and sinks to the graph
Input: graph - an instance of the Graph class
source - an array of tuples, holding source coordinates and a preset edge weight of 10000
sink - an array of tuples, holding sink coordinates and a preset edge weight of 10000
'''
s = Vertex("Source")
t = Vertex("Sink")
graph.add_vertex(s)
graph.add_vertex(t)
for edge in source:
#edge[0] describes the name of the neighboring vertex
#edge[1] describes the weight, set by default to the maximum, of 100
print "SOURCE TO: " + str(edge)
print graph.vertices["Source"]
print graph.vertices[(1,1)]
print graph.add_directed_edge("Source", edge[0], edge[1])
for edge in sink:
#edge[0] describes the name of the neighboring vertex
#edge[1] describes the weight, set by default to the maximum, of 100
graph.add_directed_edge(edge[0], "Sink", edge[1])
def create_graph(graph, filename, source, sink):
''' Creates the graph
Input: graph - an (empty) instance of the Graph class
filename - the file name or file path of the image
source - an array of tuples, holding source coordinates and a preset edge weight of 10000
sink - an array of tuples, holding sink coordinates and a preset edge weight of 10000
'''
graph = Graph()
image_array = image_to_array(filename)
add_vertices(graph, image_array)
add_edges(graph, image_array)
add_source_sink(graph, source, sink)
def dfs(graph, resGraph):
''' Our method of finding an augmenting path in the residual graph, using a depth-first-search from source to sink
Input: graph - an instance of the Graph class
resGraph - a 2D list representing the residual graph
'''
#remember that the row signifies the "pointer" and the column signifies the "pointee"
edges = graph.edges
verts = graph.vertices
#variables standard to a dfs
source = (edges[-2], len(edges)-2)
stack = []
stack.append(source)
pathTracker = {} #allows for tracking of paths, so that the obtained path can be traced from sink back to source; see https://stackoverflow.com/questions/12864004/tracing-and-returning-a-path-in-depth-first-search
verts[graph.index_to_vertex[len(edges)-2]].visited = True #setting the source to visited
allVisited = [verts[graph.index_to_vertex[len(edges)-2]]] #this array is maintained so we can easily reset the 'visited' state of every vertex at the end of execution
while len(stack) > 0: #standard dfs
top = stack.pop()
for i in range(len(top[0])):
#going through a list of neighbors, which looks like [0, 100, 0, 14, 0, etc...]
if top[0][i] is 0: #i.e. no edge
continue
if verts[graph.index_to_vertex[i]].name is 'Sink': #i.e. we've finished!
pathTracker[i] = top[1]
break
if not verts[graph.index_to_vertex[i]].visited: #i.e. an unvisited neighbor
if edges[top[1]][i] - resGraph[top[1]][i] > 0: #see if there's flow left
stack.append((edges[i], i)) #if so, push it
#print (edges[i], i)
pathTracker[i] = top[1] #inspiration from here: https://stackoverflow.com/questions/12864004/tracing-and-returning-a-path-in-depth-first-search
verts[graph.index_to_vertex[i]].visited = True #mark visited after pushing
allVisited.append(verts[graph.index_to_vertex[i]])
for x in allVisited: #clear visited, could've used a visited matrix as that would clear as soon as the method ended execution...
#print x.name
x.visited = False
path = [] #finally, get the path
current = len(edges)-1 #inspiration from here: https://stackoverflow.com/questions/12864004/tracing-and-returning-a-path-in-depth-first-search
if current not in pathTracker: #this means if the sink isn't in pathTracker, implying no path from source to sink exists...
return path #...return an empty array
while (current != len(edges)-2): #otherwise, backtrack through the dict until you get to the source
path.append(graph.index_to_vertex[current])
current = pathTracker[current]
path.append(graph.index_to_vertex[len(edges)-2]) #append the source
return path
def edmondsKarp(graph, resGraph):
''' A method that runs the actual Edmonds-Karp Algorithm.
Input: graph - an instance of the Graph class
resGraph - a 2D list representing the residual graph
'''
path = dfs(graph, resGraph) #this gets you the path, but its reversed. We handle that in the loop, by switching the order of the vertex indices when finding edge weights (i.e. graph.edges[graph.vertex_indices[path[x+1]]][graph.vertex_indices[path[x]]])
#print path
while('Sink' in path): #while an 'augmenting path' exists in the residual graph
#print "EdmondsKarp" #for testing
edges = [(graph.vertex_indices[path[x+1]],graph.vertex_indices[path[x]]) for x in range(len(path)-1)]
edges.reverse() #this gets all the edges in a simple tuple format, with integers representing the indices they correspond to in the adjacency matrix, and put in sequential order, as the dfs returns a reversed path
minCap = min([graph.edges[edge[0]][edge[1]] - resGraph[edge[0]][edge[1]] for edge in edges]) #find the minimum residual capacity
for edge in edges:
#(u,v) += minCap
resGraph[edge[0]][edge[1]] += minCap
#(v,u) -= minCap
resGraph[edge[1]][edge[0]] -= minCap
path = dfs(graph, resGraph) #update the path
def findCut(graph, resGraph):
''' A breadth-first traversal from the source to all edges that arent at capacity, making huge list of vertices/pixels that are in the source segment
Input: graph - an instance of the graph class
resGraph - a 2D list representing the residual graph, AFTER Edmonds-Karp has been run.
'''
print "Here" #for testing
pixels = [graph.vertices[graph.index_to_vertex[len(graph.edges)-2]]] #a list of all the pixels
#variables standard to a bft
q = []
source = (graph.edges[-2],len(graph.edges)-2)
q.append(source)
while len(q) > 0:
top = q.pop(0)
for i in range(len(top[0])):
if top[0][i] is not 0: #want to filter all the neighbors of the source in the residual graph such that we only go for neighbors where an edge exists, there is capacity left, and it hasn't been visited (i.e. it is a part of the source segment)
if top[0][i] - resGraph[top[1]][i] > 0: #could've easily made this 1 if statement, but splitting it like this improved readability and facilitated debugging
if not graph.vertices[graph.index_to_vertex[i]].visited:
pixels.append(graph.vertices[graph.index_to_vertex[i]]) #it is part of the source segment!
q.append((graph.edges[i], i))#if so, push it
graph.vertices[graph.index_to_vertex[i]].visited = True #mark visited after pushing
print [str(x) for x in pixels]
return pixels
# Reversing a list using reversed() - https://www.geeksforgeeks.org/python-reversing-tuple/
def Reverse(tuples):
new_tup = ()
for k in reversed(tuples):
new_tup = new_tup + (k,)
return new_tup
if __name__ == "__main__":
#get the image, sources, and sinks
pre_sources, pre_sinks, filename, dimensions = pickImage()
filename = "C:\Users\prana\Documents\image_segmentation\image_segmentation\imeg2.png"
dimensions = (10,8)
#print pre_sources
pre_sources = [x[::-1] for x in pre_sources]
#print pre_sources
#print pre_sinks
pre_sinks = [x[::-1] for x in pre_sinks]
#print pre_sinks
pre_sources = ["5,8"]
pre_sinks = ["3,2", "4,6"]
#converts the coordinates from pickImage() to a format that can be easily converted to a Vertex
sources = [((int(x.split(",")[0]), int(x.split(",")[1])),10000) for x in pre_sources]
sinks = [((int(x.split(",")[0]), int(x.split(",")[1])),10000) for x in pre_sinks]
print sources
print sinks
#create the graph
graph = Graph()
create_graph(graph, filename, sources, sinks)
#and the residual graph
residualGraph = [[0 for x in range(len(graph.edges))] for y in range(len(graph.edges))]
#graph.print_graph()
#print residualGraph
#run Edmonds-Karp
edmondsKarp(graph, residualGraph)
#finally, the bft that gets all vertices associated with pixels in the source segment/foreground
source_pixels = findCut(graph, residualGraph)
#finally, create and save an image that has a blue sink segment (a blue background) and a red source segment (a red foreground)
segmented = Image.new('RGB', dimensions, color = (0, 0, 255))
for i in source_pixels:
if not i.name == "Source":
print Reverse(i.name)
segmented.putpixel(Reverse(i.name), (255, 0, 0))
segmented.save("segmented.png")