-
Notifications
You must be signed in to change notification settings - Fork 0
/
c4cv.py
365 lines (318 loc) · 13 KB
/
c4cv.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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
import cv2
import cv2.cv as cv
import numpy as np
import random
import math
import urllib2
import string
import time
import socket
import thread
default_timeout = 12
socket.setdefaulttimeout(default_timeout)
#global parameter of piece size. in future need to learn this actively.
screenpercent = .9
piecesize = 45
color_threshold = 5000
RED_SIGN = "X" #determined by first color
BLACK_SIGN = "O" #determined by first color
MEMOIZED_TABLE = {}
BASEURL = "http://nyc.cs.berkeley.edu:8080/gcweb/service/gamesman/puzzles/connect4/getNextMoveValues;width=7;height=6;pieces=4;board="
offlinemode = 0
avgcenters = None
def board_to_response(board):
"""
Passed in board string
Memoize / ping Berkeley server and return evaluated answer
"""
global MEMOIZED_TABLE
if board in MEMOIZED_TABLE:
if MEMOIZED_TABLE[board] != "failed":
return MEMOIZED_TABLE[board]
ans = "failed"
xnum = board.count("X")
onum = board.count("O")
if not(xnum > onum+1 or xnum < onum):
try:
if(not offlinemode):
board = board.replace(" ","%20")
url = urllib2.urlopen(BASEURL + board, timeout=1)
html = url.read()
url.close()
ans = eval(html)['response']
MEMOIZED_TABLE[board] = ans
except:
print "Bad URL or timeout or response or offlinemode is on"
return ans
def determineTemplateColor(template, rad):
runningsum = (0,0,0)
cx = template.shape[0]/2
cy = template.shape[1]/2
for i in range(0,25):
#sample 25 points and get their colors
dist = random.random()*rad
angle = random.random()*3.14
x = math.cos(angle)*dist
y = math.sin(angle)*dist
color = template[cx+x,cy+y]
runningsum += color
runningsum = runningsum / 25
return runningsum
def lizFindCirclesGrid(circles):
"""
Passed in array of circles with no false positives, return array of most likely 6x7 grid centers
in raste
"""
#generate row and column delimiting values
cxs = []
cys = []
for i in circles:
cxs.append(i[0])
cys.append(i[1])
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,10,1.0)
retval_x, bestlabels_x, centers_kmeans_x = cv2.kmeans(np.array(np.float32(cxs)),7,criteria,10,cv2.KMEANS_PP_CENTERS)
retval_y, bestlabels_y, centers_kmeans_y = cv2.kmeans(np.array(np.float32(cys)),6,criteria,10,cv2.KMEANS_PP_CENTERS)
#we should see 7 groups of in x, and 6 groups in y
#delimited by a jump of one piece size in width (~30-50 pixels)
centers_kmeans_x = np.msort(centers_kmeans_x)
centers_kmeans_y = np.msort(centers_kmeans_y)
fullrow = []
fullcol = []
for i in centers_kmeans_x:
fullcol.append(int(i))
for j in centers_kmeans_y:
fullrow.append(int(j))
finalout = []
#finalout is 42 possible pairs
for i in range(5,-1,-1):
for j in range(7):
finalout.append( [fullrow[i],fullcol[j] ] )
return finalout, centers_kmeans_x, centers_kmeans_y
def drawBoardOverlay(image):
upperleft = (int(image.shape[1] * (1-screenpercent)), int(image.shape[0] * (1-screenpercent)))
lowerright = (int(image.shape[1] * screenpercent), int(image.shape[0] * screenpercent))
cv2.rectangle(image,upperleft,lowerright,(1,1,1),thickness=5)
return image
def captureImage(capture):
"""-
Grabs image from webcam, draws rectangle on it, and returns the image
"""
image = None
while(not image):
image = cv.QueryFrame(capture)
image = np.asarray(image[:,:])
return image
def gamesmanVision(capture):
"""
main function. recursively calls until escape command.
"""
testimage = 1
img = None #this is the image for analysis, cropped from original
image = None #this is the original image
blacktemp = './templates/blackpiece_template_dark.png'
redtemp = './templates/redpiece_template_dark.png'
blackcolor = determineTemplateColor(cv2.imread(blacktemp),10)
redcolor = determineTemplateColor(cv2.imread(redtemp),10)
width = 0
height = 0
if(not testimage):
#capture image from webcam, add rectangle
image = captureImage(capture)
cv2.imwrite("output.png",image) #calibration purposes
#grabs center 50% for processing
height = image.shape[1]
width = image.shape[0]
img = image[(1-screenpercent)*width:screenpercent*width, (1-screenpercent)*height:screenpercent*height]
while(cv2.waitKey(1) <= 0):
if(not testimage):
#capture image from webcam, add rectangle
image = captureImage(capture)
#grabs center 50% for processing
height = image.shape[1]
width = image.shape[0]
img = image[(1-screenpercent)*width:screenpercent*width, (1-screenpercent)*height:screenpercent*height]
#test images
if(testimage):
#image = './images/c4_template.png'
image = './images/cropped_image1.png'
#image = './images/cropped_image2.png'
#image = './images/cropped_image3.png'
#image = './images/img_2_big.jpg'
img = cv2.imread(image,1)
img = cv2.resize(img, None, fx=1/4.0, fy =1/4.0)
#img = cv2.resize(img, None, fx=1/1.5, fy =1/1.5)
edges = cv2.Canny(img,100,200)
w,h, c = img.shape
#make_circles_image
cimg = edges.copy()
circles = cv2.HoughCircles(cimg,cv.CV_HOUGH_GRADIENT,10,piecesize, param1=200,param2=100,minRadius=int(0.5*piecesize),maxRadius=int(2.0*piecesize))
try:
if(not circles):
cv2.imshow("GamesmanVision",image)
continue
except:
a = 2+2
circles = np.uint16(np.around(circles))
#do a pass and remove ones massively larger and smaller than median size
rads = []
for i in circles[0,:]:
rads.append(i[2])
rads.sort()
avgrad = float(rads[len(rads)/2])
circles_culled = []
for i in circles[0,:]:
if(i[2] > avgrad*1.5 or i[2] < avgrad/1.5):
continue
circles_culled.append(i)
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(255,255,255),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('cricles?',cimg)
dogrid = 1
centers = None
centers_kmeans_x = None
centers_kmeans_y = None
try:
(centers, centers_kmeans_x, centers_kmeans_y) = lizFindCirclesGrid(circles_culled)
except:
pass
try:
(centers, centers_kmeans_x, centers_kmeans_y) = lizFindCirclesGrid(circles_culled)
except:
dogrid = 0
if(not centers):
dogrid = 0
if(dogrid):
try:
for i in range(len(centers)):
avgcenters[i] = (avgcenters[i] + centers[i]) / 2.0
except:
avgcenters = centers[:]
centers = avgcenters[:]
#generate board string if found!
#generate sampling distance
sd = piecesize / 3
index = 0
colors_km = [] #building array for kmeans clustering of colors
for item in centers:
#circles will be in order.
runningsum = (0,0,0)
cy = item[1]
cx = item[0]
for i in range(0,25):
#sample 25 points and get their colors
dist = random.random()*sd
angle = random.random()*3.14
x = math.cos(angle)*dist
y = math.sin(angle)*dist
try:
color = img[cx+x,cy+y]
except:
continue
runningsum += color
runningsum = runningsum / 25
centers[index].append(runningsum)
runningsum = np.float32(runningsum)
colors_km.append(list(runningsum))
index += 1
#now we've got each center associated with its color
#generate groupings for red / black / empty!
colors_km = np.array(colors_km)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,10,1.0)
retval, bestlabels, centers_kmeans = cv2.kmeans(colors_km,3,criteria,10,cv2.KMEANS_PP_CENTERS)
#separate these out by ENERGY, not by index.
#sometimes opencv changes up the indicies
#find index of highest overall energy (1,1,1)
#find index of highest energy according to (-1,-1,1)
#find lowest overall energy (-1,-1,-1)
black_energy = (-1,-1,-1)
white_energy = (1,1,1)
b_max_energy = -1000000
w_max_energy = -1000000
b_ind = 0
w_ind = 0
for i in range(len(centers_kmeans)):
current_center = centers_kmeans[i]
tot_b = 0
tot_w = 0
for j in range(len(current_center)):
tot_b = black_energy[j]*pow(current_center[j],2)
tot_w = white_energy[j]*pow(current_center[j],2)
if tot_b > b_max_energy:
b_max_energy = tot_b
b_ind = i
if tot_w > w_max_energy:
w_max_energy = tot_w
w_ind = i
colors_in_order = []
for i in centers:
# draw the center of the circle
cv2.circle(img, (i[1],i[0]), 15,(255,255,255),3)
color = "shit"
color_choice = (255,0,0)
#if within threshold of blackcolor or redcolor, classify as those
#otherwise, is background
distr = pow(redcolor[0] - i[2][0],2) + pow(redcolor[1]-i[2][1],2) + pow(redcolor[2]-i[2][2],2)
distb = pow(blackcolor[0] - i[2][0],2) + pow(blackcolor[1]-i[2][1],2) + pow(blackcolor[2]-i[2][2],2)
if distr < color_threshold:
color_choice = (0,0,255)
color = "red"
elif distb < color_threshold:
color_choice = (0,0,0)
color = "black"
else:
color_choice = (255,255,255)
color = "empty"
colors_in_order.append(color)
cv2.circle(img,(i[1],i[0]),10,color_choice,3)
#from these, generate a board!
BOARD = ""
for i in colors_in_order:
if(i == "red"):
BOARD = BOARD+RED_SIGN
elif (i == "black"):
BOARD = BOARD+BLACK_SIGN
elif (i == "empty"):
BOARD = BOARD+" "
else:
BOARD = BOARD+"$" #this is debugging sign for problem
if(not testimage):
image[(1-screenpercent)*width:screenpercent*width, (1-screenpercent)*height:(screenpercent)*height] = img
img = drawBoardOverlay(image)
ans = board_to_response(BOARD)
moves = {}
if ans != "failed":
#generate list of moves and their associated color
for i in ans:
if i['value'] == "lose":
moves[i["move"]] = (0,0,255)
elif i['value'] == "win":
moves[i["move"]] = (0,255,0)
else:
moves[i["move"]] = (0,255,255)
#there will be seven valid moves (tops)
if(not testimage and ans != "failed"):
for i in range(7):
#paste them on to the full image above the board with appropriate coloring and opacity for move values!
color = (100,100,100)
if str(i) in moves:
color = moves[str(i)]
#figure out position of this rectangle
#centered at
cent = (centers_kmeans_x[i],centers_kmeans_y[0]+piecesize)
bottom_left = (cent[0]-piecesize/2, cent[1]-piecesize/2)
top_right = (cent[0]+piecesize/2, cent[1]+piecesize/2)
#these two locations relative from bottom left of sub-image
pt1 = bottom_left
pt2 = top_right
pt1 = (int(pt1[0]+height*(1-screenpercent)), int(pt1[1]-width*(1-screenpercent)))
pt2 = (int(pt2[0]+height*(1-screenpercent)), int(pt2[1]-width*(1-screenpercent)))
cv2.rectangle(img,pt1,pt2,color,-1)
img = cv2.resize(img, None, fx=1.5, fy =1.5)
cv2.imshow('GamesmanVision',img)
#clean up
cv2.destroyAllWindows()
del(capture)
capture = cv.CaptureFromCAM(1)
gamesmanVision(capture)