forked from MemExplorer/poker-vision
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpoker_image_processor.py
199 lines (155 loc) · 7.02 KB
/
poker_image_processor.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
import cv2
import numpy as np
from functools import cmp_to_key
#constants
CARD_MAX_AREA = 10000000
CARD_MIN_AREA = 15000 #5000 if 1280x720
def flattener(image, pts, w, h):
"""Flattens an image of a card into a top-down 200x300 perspective.
Returns the flattened, re-sized, grayed image.
See www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example/"""
temp_rect = np.zeros((4,2), dtype = "float32")
s = np.sum(pts, axis = 2)
tl = pts[np.argmin(s)]
br = pts[np.argmax(s)]
diff = np.diff(pts, axis = -1)
tr = pts[np.argmin(diff)]
bl = pts[np.argmax(diff)]
# Need to create an array listing points in order of
# [top left, top right, bottom right, bottom left]
# before doing the perspective transform
if w <= 0.8*h: # If card is vertically oriented
temp_rect[0] = tl
temp_rect[1] = tr
temp_rect[2] = br
temp_rect[3] = bl
if w >= 1.2*h: # If card is horizontally oriented
temp_rect[0] = bl
temp_rect[1] = tl
temp_rect[2] = tr
temp_rect[3] = br
# If the card is 'diamond' oriented, a different algorithm
# has to be used to identify which point is top left, top right
# bottom left, and bottom right.
if w > 0.8*h and w < 1.2*h: #If card is diamond oriented
# If furthest left point is higher than furthest right point,
# card is tilted to the left.
if pts[1][0][1] <= pts[3][0][1]:
# If card is titled to the left, approxPolyDP returns points
# in this order: top right, top left, bottom left, bottom right
temp_rect[0] = pts[1][0] # Top left
temp_rect[1] = pts[0][0] # Top right
temp_rect[2] = pts[3][0] # Bottom right
temp_rect[3] = pts[2][0] # Bottom left
# If furthest left point is lower than furthest right point,
# card is tilted to the right
if pts[1][0][1] > pts[3][0][1]:
# If card is titled to the right, approxPolyDP returns points
# in this order: top left, bottom left, bottom right, top right
temp_rect[0] = pts[0][0] # Top left
temp_rect[1] = pts[3][0] # Top right
temp_rect[2] = pts[2][0] # Bottom right
temp_rect[3] = pts[1][0] # Bottom left
maxWidth = w
maxHeight = h
# Create destination array, calculate perspective transform matrix,
# and warp card image
dst = np.array([[0,0],[maxWidth-1,0],[maxWidth-1,maxHeight-1],[0, maxHeight-1]], np.float32)
M = cv2.getPerspectiveTransform(temp_rect,dst)
warp = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
return warp
# necessary transformations to get card contours easily
def process_card_image(image):
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
#separate foreground objects from background
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
return thresh
# compare placement of 2 cards
def contour_sort(a, b):
br_a = cv2.boundingRect(a)
br_b = cv2.boundingRect(b)
if abs(br_a[1] - br_b[1]) <= 15:
return br_a[0] - br_b[0]
return br_a[1] - br_b[1]
def get_card_contours(thresh):
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
# cards must be less than half of image width
half = cv2.boundingRect(thresh)[2] * 0.6
# find valid card contours
valid_conts = []
for i in range(len(cnts)):
size = cv2.contourArea(cnts[i])
peri = cv2.arcLength(cnts[i],True)
approx = cv2.approxPolyDP(cnts[i],0.01*peri,True)
if ((size < CARD_MAX_AREA) and (size > CARD_MIN_AREA)
and (len(approx) == 4) and cv2.boundingRect(cnts[i])[2] < half):
valid_conts.append(cnts[i])
#sort the detected cards by their area in descending order
valid_conts.sort(key=lambda x: cv2.contourArea(x), reverse=True)
#iterate through the sorted cards and exclude detection inside another detection
valid_conts2 = []
for i, card in enumerate(valid_conts):
is_inside = False
for j, other_card in enumerate(valid_conts):
if i != j:
ptt = tuple([int(round(card[0][0][0])), int(round(card[0][0][1]))])
result = cv2.pointPolygonTest(other_card, ptt, False)
if result == 1:
is_inside = True
break
if not is_inside:
valid_conts2.append(card)
# sort cards from top, left to right, then botton, left to right
return sorted(valid_conts2, key=cmp_to_key(contour_sort))
def get_contour_points(contour):
#find perimeter of card and use it to approximate corner points
peri = cv2.arcLength(contour,True)
approx = cv2.approxPolyDP(contour,0.01*peri,True)
pts = np.float32(approx)
return pts
def flatten_perspective_transform(contour, image):
pts = get_contour_points(contour)
c = cv2.boundingRect(contour)
flattened_image = flattener(image, pts, c[2], c[3])
return flattened_image
def remove_spaces(image):
#find the largest contour
card_rank_contours = cv2.findContours(image, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[0]
#sort to get the highest value at first index
card_rank_contours = sorted(card_rank_contours, key=cv2.contourArea,reverse=True)
#return something if we find any contours
if len(card_rank_contours) != 0:
x1,y1,w1,h1 = cv2.boundingRect(card_rank_contours[0])
cropped_object = image[y1:y1+h1, x1:x1+w1]
resized_picture = cv2.resize(cropped_object, (70, 125), 0, 0)
return resized_picture
def get_corner_info_image(flattened_img):
#crop card info part of the card
x,y,w,h = cv2.boundingRect(flattened_img)
y_percent = 0.26
x_percent = 0.15
c_width = int(w * x_percent)
# handle image transition error
if c_width == 0:
return (None, None)
cropped_info = flattened_img[y:y+int(h * y_percent), x:x+c_width]
resized_card_info_img = cv2.resize(cropped_info, (0,0), fx=4, fy=4)
#update values to calculate card info image size
x,y,w,h = cv2.boundingRect(resized_card_info_img)
cent_y = int(h * 0.5)
#get thresh level
white_level = resized_card_info_img[15,int((c_width*4)/2)]
thresh_level = white_level - 60
if (thresh_level <= 0):
thresh_level = 1
blur = cv2.GaussianBlur(resized_card_info_img,(3,3),0)
rank_threshed = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 27, 5)
val_threshed = cv2.threshold(resized_card_info_img, thresh_level, 255, cv2.THRESH_BINARY_INV)[1]
#split card symbol and value
card_value_img = remove_spaces(rank_threshed[10:cent_y + 10, 0:w])
card_shape_img = remove_spaces(val_threshed[cent_y:cent_y + (h - cent_y), 0:w])
# small hack to fix detection issues
if card_shape_img is None:
card_shape_img = remove_spaces(rank_threshed[cent_y:cent_y + (h - cent_y), 0:w])
return (card_value_img, card_shape_img)