Album
米Teen唔鐘意玩現成品,喜歡由零件開始砌。99次失敗唔係問題,最緊要係有1次成功!
米Teen總部設在聖公會白約翰會督中學506室,在午膳後和放學後集會。有興趣參與的同學,請與會長或導師聯絡。
506室內 2020-11-04 21:43:57
溫度 26℃ 濕度 95%
街景 2020-11-04 21:41:52
溫度 26℃ 濕度 95%
街景 2020-11-04 21:41:52
5樓室外 2020-11-04 21:43:43
溫度 22℃ 濕度 95%
街景 2020-11-04 21:34:34
溫度 22℃ 濕度 95%
街景 2020-11-04 21:34:34
同Dobot機械臂玩過三關
同Dobot機械臂玩...
> >用Python程式控制Dobot機械臂同你玩過三關。用Python程式編寫過三關戰術,控制Dobot機械臂執放與移動棋子,透過Webcam送回的影像偵測棋盤和棋子,辨認人類對手的棋路,讓Dobot機械臂與人對弈。本製作被邀請在2019年6月的香港電訊5G科技嘉年華展出。
系統規格如下:
編程語言:Python 機械臂:Dobot Magician 影像:Webcam
開發程序
- 用Python控制Dobot移動及使用吸咀運送棋子
- 用Python編寫過三關戰術
- 用Python分析WebCam傳送回來的影像
- 用Python辨認棋盤上的棋子
- 用Python控制流程
項目負責同學:4C洪子洛
用Python程式控制Dobot機械臂搬動棋子的試驗
剛完成了玩過三關的程式,還要debug,稍後再優化程序。
Author: HUNG TSZ LOK (SKH Bishop Baker Secondary School, class 4C)
Last modified: 2019 June
1 | #pip install pydobot |
2 | import cv2 |
3 | import random |
4 | import time |
5 | import numpy as np |
6 | from pydobot import Dobot |
7 | |
8 | # connect and setup DOBOT |
9 | d = Dobot('COM5', 0) |
10 | d._set_end_effector_suction_cup(False) |
11 | d._set_ptp_coordinate_params(150, 150) |
12 | d._set_ptp_common_params(150, 100) |
13 | |
14 | |
15 | print('start') |
16 | movex = { |
17 | '000000000': [0, 4, 2, 4, 6, 8], '100020000': [8], '001020000': [6], |
18 | '000020100': [2], '000020001': [0], '020010000': [0, 2], |
19 | '000210000': [0, 6], '000012000': [2, 8], '000010020': [6, 8], |
20 | '120010002': [3, 6], '021010200': [5, 8], '002210100': [7, 8], |
21 | '100210002': [1, 2], '001012200': [1], '200012001': [7], '200010021': [5], |
22 | '002010120': [3], '000020000': [0, 2, 6, 8], '200000000': [4], |
23 | '002000000': [4], '000000200': [4], '000000002': [4], |
24 | '002010200': [1, 3, 5, 7], '200010002': [1, 3, 5, 7], '000200100': [8], |
25 | '000000021': [2], '001002000': [0], '120000000': [6], '000200121': [2], |
26 | '001002021': [0], '121002000': [6], '120200100': [8], '000000120': [0], |
27 | '000002001': [6], '021000000': [8], '100200000': [2], '100200120': [2], |
28 | '000002121': [0], '021002001': [6], '121200000': [8]} |
29 | |
30 | |
31 | class Checkerboard: |
32 | def __init__( |
33 | self, test=0, z=-70, x=265.3, y=45.5, x_offset=-35.8, y_offset=-39, |
34 | home=[140, 100, 0], first=1, r_value=1500, b_value=1500, upr=0.0, |
35 | cx=50, cy=50): |
36 | self.test = test |
37 | self.r_value = r_value # color area threshold |
38 | self.b_value = b_value # '' |
39 | |
40 | self.round = 0 # moved count |
41 | self.first = first # is bot first |
42 | self.blankcount = 0 |
43 | self.x, self.y, self.z = x, y, z # grid #1 coordinates (top left of Dobot #1) |
44 | |
45 | # ** x, y, x_offset, y_offset must be as precise as possible for Dobot #1. |
46 | |
47 | self.upr = upr # unit pixel ratio |
48 | self.cx, self.cy = cx, cy # grid #1 center image coordinates (normally (55, 55)) |
49 | self.x_offset, self.y_offset = x_offset, y_offset |
50 | self.xupr, self.yupr = self.x_offset / 95, self.y_offset / 95 # unit pixel ratio |
51 | self.cap = cv2.VideoCapture(0) |
52 | self.home = home # home position |
53 | self.sit = 0 # situation id |
54 | self.pos = [(self.x, self.y+self.y_offset*3), |
55 | (self.x+self.x_offset, self.y+self.y_offset*3), |
56 | (self.x+2*x_offset, self.y+self.y_offset*3), |
57 | (self.x, self.y-self.y_offset), |
58 | (self.x+self.x_offset, self.y-self.y_offset), |
59 | (self.x+2*self.x_offset, self.y-self.y_offset)] # default chess coordinates |
60 | self.checkattemp = 0 # saving checks |
61 | self.checkerboard = '0' * 9 # checkerboard with color id |
62 | self.checkerboardchecks = ['0' * 9] * 3 # list of self.checkattemp |
63 | self.grid = np.array([[0, 100, 200, 300], [0, 100, 200, 300]]) # cropping |
64 | |
65 | def c(self): # capture the checkerboard |
66 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
67 | self.frame = self.cap.read()[1] |
68 | |
69 | def com(self): # compare new and old checkerboard |
70 | if (sum(map(lambda c0, c1, c2: c0 == c1 == c2, |
71 | self.checkerboardchecks[0], self.checkerboardchecks[1], |
72 | self.checkerboardchecks[2])) == 9): # 3 same check results of new |
73 | different = sum(map(lambda c, c0: c != c0, self.checkerboard, |
74 | self.checkerboardchecks[0])) # difference of old and new |
75 | if different == 1: |
76 | if ( |
77 | sum([not int(x) for x in self.checkerboard if x == '0'] |
78 | ) - sum([not int(x) for x in self.checkerboardchecks[0] |
79 | if x == '0']) == 1): # grid with chess different count = -1 |
80 | return True |
81 | else: |
82 | return False |
83 | elif different == 0: |
84 | return False |
85 | else: |
86 | pass |
87 | else: |
88 | return False |
89 | |
90 | def cnt(self, src, lmask, umask, lmask1=0, umask1=0, getCenter=False): # find contour |
91 | blurred = cv2.GaussianBlur(src, (5, 5), 0) |
92 | hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) |
93 | |
94 | mask = cv2.inRange(hsv, lmask, umask) |
95 | if lmask1 + umask1 is not 0: |
96 | mask1 = cv2.inRange(hsv, lmask1, umask1) |
97 | mask = mask + mask1 |
98 | contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, |
99 | cv2.CHAIN_APPROX_NONE)[1] |
100 | if len(contours) > 0: |
101 | contour = max(contours, key=cv2.contourArea) # only return the contour with max area |
102 | area = cv2.contourArea(contour) |
103 | if getCenter: |
104 | Moments = cv2.moments(contour) |
105 | if Moments['m00'] != 0: |
106 | cX = int(Moments['m10'] / Moments['m00']) |
107 | cY = int(Moments['m01'] / Moments['m00']) |
108 | else: |
109 | cX, cY = (self.cx, self.cy) |
110 | return contour, area, (cX, cY) |
111 | else: |
112 | return contour, area |
113 | else: |
114 | if getCenter: |
115 | return 0, 0, (False, False) |
116 | else: |
117 | return 0, 0 |
118 | |
119 | def cntcenter(self, src, threshold, lmask, umask, lmask1=0, umask1=0): # find contour of all placed chess and get their centers |
120 | blurred = cv2.GaussianBlur(src, (5, 5), 0) |
121 | hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) |
122 | |
123 | mask = cv2.inRange(hsv, lmask, umask) |
124 | if lmask1 + umask1 is not 0: |
125 | mask1 = cv2.inRange(hsv, lmask1, umask1) |
126 | mask = mask + mask1 |
127 | contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, |
128 | cv2.CHAIN_APPROX_NONE)[1] |
129 | |
130 | self.centers = [] |
131 | if len(contours) > 0: |
132 | for contour in contours: |
133 | if cv2.contourArea(contour) < threshold: |
134 | continue |
135 | else: |
136 | Moments = cv2.moments(contour) |
137 | cX = int(Moments['m10'] / Moments['m00']) |
138 | cY = int(Moments['m01'] / Moments['m00']) |
139 | center = (cX, cY) |
140 | self.centers.append(center) |
141 | return self.centers |
142 | |
143 | def transform(self): # matrix perspective transform of image |
144 | global initstat, pts1 |
145 | contour, area = self.cnt(self.frame, np.array([0, 0, 0]), |
146 | np.array([255, 150, 75])) |
147 | print('board area = ', area) |
148 | if area < 10000: |
149 | return True |
150 | pass |
151 | |
152 | epsilon = 0.05*cv2.arcLength(contour, True) |
153 | approx = cv2.approxPolyDP(contour, epsilon, True) |
154 | |
155 | if initstat or True: |
156 | pts1 = np.float32(approx) |
157 | initstat = False |
158 | pts2 = np.float32([[0, 0], [0, 300], [300, 300], [300, 0]]) |
159 | M = cv2.getPerspectiveTransform(pts1, pts2) |
160 | self.frame = cv2.warpPerspective(self.frame, M, (300, 300)) |
161 | |
162 | self.grids = [] |
163 | for i in range(9): |
164 | self.grids.append( |
165 | self.frame[self.grid[0, i // 3]:self.grid[0, i // 3+1], |
166 | self.grid[1, i % 3]:self.grid[1, i % 3+1]]) |
167 | |
168 | def check(self, delay=0, startc=0, getCenter=0): # recognize the checkerboard |
169 | global initstat |
170 | self.c() |
171 | try: |
172 | if self.transform(): |
173 | return False |
174 | except cv2.error: |
175 | initstat = True |
176 | print('camera blocked') |
177 | return False |
178 | |
179 | '''if startc: |
180 | return np.std(self.frame, 2).std() < 12''' |
181 | self.centers = {} |
182 | self.checkattemp += 1 |
183 | checking = '' |
184 | print('red area blue area') |
185 | for gn, i in zip(self.grids, range(9)): |
186 | # blue |
187 | lower_blue = np.array([90, 100, 90]) |
188 | upper_blue = np.array([110, 255, 255]) |
189 | # red |
190 | lower_red = np.array([0, 100, 90]) |
191 | upper_red = np.array([10, 255, 255]) |
192 | lower_red1 = np.array([170, 100, 90]) |
193 | upper_red1 = np.array([180, 255, 255]) |
194 | |
195 | if getCenter: |
196 | self.blue_centers = self.cntcenter( |
197 | self.frame, self.b_value, lower_blue, upper_blue) |
198 | |
199 | self.red_centers = self.cntcenter( |
200 | self.frame, self.r_value, lower_red, upper_red, lower_red1, upper_red1,) |
201 | |
202 | print(self.blue_centers, '\n', self.red_centers) |
203 | return self.blue_centers, self.red_centers |
204 | else: |
205 | contour_blue, area_blue = self.cnt(gn, lower_blue, upper_blue) |
206 | contour_red, area_red = self.cnt( |
207 | gn, lower_red, upper_red, lower_red1, upper_red1) |
208 | print(area_red, area_blue, sep=' ') |
209 | |
210 | if area_red > self.r_value and area_blue < self.b_value: |
211 | checking += '1' |
212 | elif area_red < self.r_value and area_blue > self.b_value: |
213 | checking += '2' |
214 | elif area_red < self.r_value and area_blue < self.b_value: |
215 | checking += '0' |
216 | else: |
217 | checking += '3' |
218 | |
219 | print('checking = ', checking) |
220 | if startc: |
221 | if checking == '000000000': |
222 | return True |
223 | self.checkerboardv = np.array( |
224 | [int(x) for x in self.checkerboard]).reshape(3, 3) |
225 | if '3' in checking: |
226 | return False |
227 | if checking == '000000000' and self.first == 1 and self.round == 0: |
228 | self.checkerboard = checking |
229 | return True |
230 | else: |
231 | self.checkerboardchecks[self.checkattemp % 3] = checking |
232 | if checking == '000000000': |
233 | if self.checkerboard != '000000000': |
234 | self.blankcount += 1 |
235 | else: |
236 | self.blankcount = 0 |
237 | if self.com(): |
238 | self.checkerboard = checking |
239 | return True |
240 | else: |
241 | return False |
242 | |
243 | def rows(self): # divide the checkerboard into 8 rows |
244 | self.checkerboardv = np.array( |
245 | [int(x) for x in self.checkerboard]).reshape(3, 3) |
246 | self.r = list() |
247 | self.r.append((self.checkerboardv[0], 0)) |
248 | self.r.append((self.checkerboardv[1], 1)) |
249 | self.r.append((self.checkerboardv[2], 2)) |
250 | self.r.append((self.checkerboardv[:, 0], 3)) |
251 | self.r.append((self.checkerboardv[:, 1], 4)) |
252 | self.r.append((self.checkerboardv[:, 2], 5)) |
253 | self.r.append((self.checkerboardv.diagonal(), 6)) |
254 | self.r.append((np.flip(self.checkerboardv, 1).diagonal(), 7)) |
255 | |
256 | def situation(self): |
257 | self.sit = 0 |
258 | self.rows() |
259 | for rn in self.r: |
260 | if np.count_nonzero(rn[0] == 2) == 3: # lose |
261 | self.sit = 7 |
262 | return self.sit |
263 | elif np.count_nonzero(rn[0] == 1) == 3: # win |
264 | self.sit = 8 |
265 | return self.sit |
266 | if np.count_nonzero(self.checkerboardv) == 9: # draw |
267 | self.sit = 6 |
268 | return self.sit |
269 | |
270 | if self.sit == 0: |
271 | for rn in self.r: |
272 | if np.count_nonzero(rn[0] == 0): # 0xx |
273 | self.sit = 0 |
274 | self.move = [rn[1], int(np.where(rn[0] == 0)[0][0])] |
275 | else: |
276 | pass |
277 | |
278 | for rn in self.r: |
279 | if (np.count_nonzero( |
280 | rn[0] == 2) == 2 and np.count_nonzero( |
281 | rn[0] == 0) == 1): # 220 |
282 | self.sit = 1 |
283 | self.move = [rn[1], int(np.where(rn[0] == 0)[0][0])] |
284 | break |
285 | |
286 | for rn in self.r: |
287 | if (np.count_nonzero( |
288 | rn[0] == 0) == 1 and np.count_nonzero( |
289 | rn[0] == 1) == 2): # 110 |
290 | self.sit = 2 |
291 | self.move = [rn[1], int(np.where(rn[0] == 0)[0][0])] |
292 | break |
293 | |
294 | if self.checkerboard in movex: |
295 | self.move = random.choice(movex[self.checkerboard]) |
296 | self.move = [self.move // 3, self.move % 3] |
297 | else: |
298 | if self.move[0] in range(0, 3): |
299 | pass |
300 | elif self.move[0] in range(3, 6): |
301 | self.move = [self.move[1], self.move[0] - 3] |
302 | elif self.move[0] == 6: |
303 | self.move = [self.move[1], self.move[1]] |
304 | elif self.move[0] == 7: |
305 | self.move = [self.move[1], 2 - self.move[1]] |
306 | |
307 | return self.sit |
308 | |
309 | def intention(self): |
310 | self.coor = [self.x + self.x_offset * self.move[0], |
311 | self.y + self.y_offset * self.move[1]] |
312 | print(self.move, self.coor, sep=' ') |
313 | return self.coor |
314 | |
315 | def m(self): # move |
316 | # get |
317 | d._set_end_effector_suction_cup(False) |
318 | d._set_ptp_cmd(160, 0, 0, 0, 0x02) # # |
319 | d._set_ptp_cmd(self.pos[self.round][0], self.pos[self.round][1], |
320 | self.z, 0, 0x00) |
321 | d._set_end_effector_suction_cup(True) |
322 | time.sleep(1) |
323 | |
324 | # place |
325 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z, 0, 0x00) |
326 | d._set_end_effector_suction_cup(False) |
327 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z + 20, 0, 0x02) |
328 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
329 | |
330 | self.checkerboard = self.checkerboard[ |
331 | :self.move[0] * 3 + self.move[1]] + '1' + self.checkerboard[ |
332 | self.move[0] * 3 + self.move[1] + 1:] |
333 | self.round += 1 |
334 | |
335 | def start(self): # start game setup |
336 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
337 | time.sleep(3) |
338 | while True: |
339 | if self.check(0, 1): |
340 | print('bot first = ', self.first) |
341 | return 'Game Start' |
342 | else: |
343 | print('please clear the board.') |
344 | time.sleep(1.5) |
345 | |
346 | def end(self): # end game and clear checkerboard |
347 | if self.sit < 5: |
348 | # |
349 | pass |
350 | else: |
351 | while self.check(getCenter=True) is False: |
352 | pass |
353 | self.reds = np.where(self.checkerboardv.reshape(9) == 1)[0] |
354 | self.blues = np.where(self.checkerboardv.reshape(9) == 2)[0] |
355 | if self.sit == 9: |
356 | print('interrupted') |
357 | |
358 | if self.sit == 8: |
359 | print('win') |
360 | |
361 | elif self.sit == 7: |
362 | print('lose') |
363 | |
364 | elif self.sit == 6: |
365 | print('draw') |
366 | |
367 | d._set_ptp_cmd(160, 0, 0, 0, 0x02) # # |
368 | for i, j in zip(self.red_centers, range(len(self.red_centers))): # red |
369 | # get |
370 | self.coor = [self.x + (i[1] - self.cx) * self.xupr, |
371 | self.y + (i[0] - self.cy) * self.yupr] |
372 | |
373 | d._set_end_effector_suction_cup(False) |
374 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z, 0, 0x00) |
375 | d._set_end_effector_suction_cup(True) |
376 | time.sleep(1) |
377 | |
378 | # place |
379 | d._set_ptp_cmd(self.pos[j][0], self.pos[j][1], self.z, 0, 0x00) |
380 | d._set_end_effector_suction_cup(False) |
381 | |
382 | for i, j in zip(self.blue_centers, range(len(self.blue_centers))): # blue |
383 | # get |
384 | self.coor = [self.x + (i[1] - self.cx) * self.xupr, |
385 | self.y + (i[0] - self.cy) * self.yupr] |
386 | |
387 | d._set_end_effector_suction_cup(False) |
388 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z, 0, 0x00) |
389 | d._set_end_effector_suction_cup(True) |
390 | time.sleep(1) |
391 | |
392 | # place |
393 | d._set_ptp_cmd(self.pos[-1][0], self.pos[-1][1], self.z, 0, 0x00) |
394 | |
395 | d._set_end_effector_suction_cup(False) |
396 | |
397 | d._set_ptp_cmd(self.pos[-1][0], self.pos[-1][1], self.z + 20, 0, |
398 | 0x02) |
399 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
400 | self.first = True if self.sit == 9 else not self.first |
401 | self.sit == 0 |
402 | self.checkerboardv, self.checkerboard = np.array( |
403 | [[0, 0, 0], [0, 0, 0], [0, 0, 0]]), '0' * 9 |
404 | |
405 | self.start() |
406 | self.checkerboard = '0' * 9 |
407 | self.checkerboardchecks = ['0' * 9] * 3 |
408 | self.checkattemp = 0 |
409 | self.round = 0 |
410 | self.blankcount = 0 |
411 | |
412 | |
413 | # main |
414 | def main(): |
415 | global board |
416 | board = Checkerboard(1) |
417 | board.start() |
418 | while True: |
419 | time.sleep(0.5) |
420 | print(board.checkerboard) |
421 | try: |
422 | if board.round % 2 != board.first or True: # turn |
423 | if board.check(): |
424 | # cv2.imshow('frame', board.frame) |
425 | # board.situation() |
426 | # board.intention() |
427 | if board.situation() < 5: |
428 | board.intention() |
429 | board.m() |
430 | time.sleep(6) |
431 | elif board.sit > 5: |
432 | board.end() |
433 | time.sleep(5 + board.round * 10) |
434 | else: |
435 | pass |
436 | if board.situation() > 5: |
437 | board.end() |
438 | if board.blankcount >= 10: |
439 | board.sit = 9 |
440 | board.end() |
441 | |
442 | except KeyboardInterrupt: |
443 | return None |
444 | |
445 | |
446 | if __name__ == '__main__': |
447 | initstat = True |
448 | main() |
449 | pass |
Album
米Teen唔鐘意玩現成品,喜歡由零件開始砌。99次失敗唔係問題,最緊要係有1次成功!
米Teen總部設在聖公會白約翰會督中學506室,在午膳後和放學後集會。有興趣參與的同學,請與會長或導師聯絡。
506室內 2020-11-04 21:43:57
溫度 26℃ 濕度 95%
街景 2020-11-04 21:41:52
溫度 26℃ 濕度 95%
街景 2020-11-04 21:41:52
5樓室外 2020-11-04 21:43:43
溫度 22℃ 濕度 95%
街景 2020-11-04 21:34:34
溫度 22℃ 濕度 95%
街景 2020-11-04 21:34:34