-
Notifications
You must be signed in to change notification settings - Fork 0
/
game_hand.py
300 lines (235 loc) · 10.8 KB
/
game_hand.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This is a sample Project for using MediaPipe in Game.
# And this is for teaching, so this is only demo, not a real game.
import random
import cv2
import numpy
import time
from PIL import Image, ImageDraw, ImageFont
import mediapipe_hand
import queue
import os
# 強制設定遊戲視窗大小
FRAME_WIDTH = 640
FRAME_HEIGHT = 480
# 設定遊玩次數
game_total_count = 10
# 設定每次活動秒數
game_play_time = 7
# 得分紀錄
total_score = 0
# 紀錄遊戲過程的 queue
game_movie_queue = None
# 錄影存檔的檔名
game_save_file_name = None
# 框之大小,用一半的量,比較好計算左上右下
rect_box_width_half = 75
rect_box_height_half = 100
# 右手相關資訊
right_hand_line_color = (255, 255, 0)
right_hand_fill_color = (80, 80, 0)
right_hand_width = 10
right_hand_mark = "右"
# 右邊框的起始位置
right_box_x = 480
right_box_y = 240
# 右手是否在右邊框
right_hand_in_box = False
# 左手相關資訊
left_hand_line_color = (0, 255, 255)
left_hand_fill_color = (0, 80, 80)
left_hand_width = 10
left_hand_mark = "左"
# 左邊框的起始位置
left_box_x = 160
left_box_y = 240
# 左手是否在左邊框
left_hand_in_box = False
# 不填滿框的內容
no_fill_color = None
# 中文字型檔
font_text_24 = ImageFont.truetype("jf-openhuninn-1.1.ttf", 24, encoding="utf-8")
font_text_36 = ImageFont.truetype("jf-openhuninn-1.1.ttf", 36, encoding="utf-8")
font_text_48 = ImageFont.truetype("jf-openhuninn-1.1.ttf", 48, encoding="utf-8")
# 顯示中文字之副程式
def add_chinese_font_to_image(img, text, left, top, text_color=(0, 255, 0), font_text=font_text_24):
# 判斷圖片格式,若為 OpenCV 格式,就做陣列與 BGR->RGB 的轉變
if isinstance(img, numpy.ndarray):
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
# 將 img 切換成 PIL 格式
draw = ImageDraw.Draw(img)
# 利用 draw 進行文字繪製
draw.text((left, top), text, text_color, font=font_text)
# 返回繪製好的 圖片+文字
return cv2.cvtColor(numpy.asarray(img), cv2.COLOR_RGB2BGR)
# 畫左右兩框
def draw_rect_box(image, in_r_rect, in_l_rect):
# 判斷圖片格式,若為 OpenCV 格式,就做陣列與 BGR->RGB 的轉變
if isinstance(image, numpy.ndarray):
image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
# 將 image 切換成 pil 格式
draw = ImageDraw.Draw(image)
# 計算 r-xy 座標
r_up_x = int(right_box_x - rect_box_width_half)
r_up_y = int(right_box_y - rect_box_height_half)
r_down_x = int(right_box_x + rect_box_width_half)
r_down_y = int(right_box_y + rect_box_height_half)
# 計算 l-xy 座標
l_up_x = int(left_box_x - rect_box_width_half)
l_up_y = int(left_box_y - rect_box_height_half)
l_down_x = int(left_box_x + rect_box_width_half)
l_down_y = int(left_box_y + rect_box_height_half)
# 繪製 right 的框
if in_r_rect:
draw.rectangle((r_up_x, r_up_y, r_down_x, r_down_y), outline=right_hand_line_color, fill=no_fill_color, width=7)
else:
draw.rectangle((r_up_x, r_up_y, r_down_x, r_down_y), outline=right_hand_line_color, fill=no_fill_color, width=2)
# 標上 right 文字
draw.text((r_up_x+10, r_up_y+10), right_hand_mark, right_hand_line_color, font=font_text_36)
# 繪製 left 的框
if in_l_rect:
draw.rectangle((l_up_x, l_up_y, l_down_x, l_down_y), outline=left_hand_line_color, fill=no_fill_color, width=7)
else:
draw.rectangle((l_up_x, l_up_y, l_down_x, l_down_y), outline=left_hand_line_color, fill=no_fill_color, width=2)
# 標上 left 文字
draw.text((l_up_x+10, l_up_y+10), left_hand_mark, left_hand_line_color, font=font_text_36)
# 返回繪製好的 圖片+框
return cv2.cvtColor(numpy.asarray(image), cv2.COLOR_RGB2BGR)
# 使用 Random 隨機定義左右手框的位置
def random_get_box_xy():
random.seed()
# 中心點的隨機值,必須要符合框能劃入的大小
a_right_box_x = random.randint(rect_box_width_half, FRAME_WIDTH - rect_box_width_half)
a_right_box_y = random.randint(rect_box_height_half, FRAME_HEIGHT - rect_box_height_half)
a_left_box_x = random.randint(rect_box_width_half, FRAME_WIDTH - rect_box_width_half)
a_left_box_y = random.randint(rect_box_height_half, FRAME_HEIGHT - rect_box_height_half)
return a_right_box_x, a_right_box_y, a_left_box_x, a_left_box_y
# 確認存檔檔名
def decide_file_name():
global game_save_file_name
# 依序找看看檔銘是否存在,若不存在則用此檔名
origin_index = 0
while True:
temp_filename = "H" + ("%05d" % origin_index) + ".mp4"
if os.path.isfile(temp_filename):
origin_index += 1
else:
game_save_file_name = temp_filename
break
# 遊戲的入口點
def play_game():
global right_box_x, right_box_y, left_box_x, left_box_y, total_score, game_movie_queue
# 是否從一般模式進入 Game Mode
is_first_in_game_mode = True
# 紀錄目前是否為遊戲模式,還是只顯示畫面
is_game_mode = False
# 紀錄目前已經經歷了幾次
play_loop_count = 0
# 記錄每一個 loop 開始的時間
begin_time = 0
# 記錄每一個畫面當下的時間
now_time = 0
# 建立存放預先錄影用 image 的 queue
game_movie_queue = queue.Queue(100)
# 錄影格式
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
# 錄影串流
out_movie = None
# 開啟攝影機
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
# 設定攝影機尺寸為固定大小,以便標示方框與文字
cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT)
# 進行不斷循環的撥放
while True:
# 擷取影像,在 play_game 這一層中, image 都要保持 OpenCV 格式
# 其他副程式若用到 Pillow,則自行轉換,但返回值仍要保持 OpenCV(BGR) 格式
success, image = cap.read()
# 若有攫取到畫面,就在視窗中顯示
if success:
# 用 WebCam 拍自己,所以先做水平翻轉
image = cv2.flip(image, 1)
# 若在遊戲模式中
if is_game_mode:
# 第一次開始到 game mode 時要作的事
if is_first_in_game_mode:
# 關閉第一次的標記
is_first_in_game_mode = False
# 開始錄影 - 決定檔名
decide_file_name()
# 取得影像的高與寬
image_height, image_width, _ = image.shape
# 開啟影片檔
out_movie = cv2.VideoWriter(game_save_file_name, fourcc, 30.0, (image_width, image_height))
# 先將 Queue 中所有的影像寫到檔案中
while not game_movie_queue.empty():
frame = game_movie_queue.get_nowait()
out_movie.write(frame)
# 每一個 game loop 的開始
if begin_time == 0:
begin_time = time.time()
now_time = time.time()
right_box_x, right_box_y, left_box_x, left_box_y = random_get_box_xy()
# 取得時間與計算間隔秒數
time_interval = round(now_time - begin_time, 1)
now_time = time.time()
# 先進行手部位置評估
image, in_r_rect, in_l_rect = mediapipe_hand.hands_estimation(image, right_box_x, right_box_y, left_box_x, left_box_y)
# 依照左右手位置的條件來畫框
image = draw_rect_box(image, in_r_rect, in_l_rect)
# 顯示目前為第幾輪的遊戲,倒數時間與分數
image = add_chinese_font_to_image(image, "手忙腳亂:第" + str(play_loop_count + 1) + "輪 已得分=" + str(total_score) + "分", 10, 10, font_text=font_text_36)
image = add_chinese_font_to_image(image, "時間倒數 " + str(int(game_play_time - time_interval)) + " 秒", 10, 70, font_text=font_text_36)
# 當秒數為 game_play_time 時,更新次數與回覆時間值
if time_interval >= game_play_time:
begin_time = 0
play_loop_count += 1
# 此時若左右手都在框中,就算各得一分
if in_r_rect:
total_score += 1
if in_l_rect:
total_score += 1
right_box_x, right_box_y, left_box_x, left_box_y = random_get_box_xy()
# 當已經跑 total 次時就回到一般模式
if play_loop_count == game_total_count:
is_game_mode = False
# 右邊框的起始位置
right_box_x = 480
right_box_y = 240
# 左邊框的起始位置
left_box_x = 160
left_box_y = 240
else:
# 關閉錄影串流
if out_movie is not None:
out_movie.release()
out_movie = None
# 若在一般模式之下,顯示啟動畫面
image = add_chinese_font_to_image(image, "手忙腳亂:請將左右手掌放於框中開始", 10, 10, font_text=font_text_36)
image = add_chinese_font_to_image(image, "上次得分:" + str(total_score) + "分", 150, 50, font_text=font_text_48)
# 先進行手部位置評估
image, in_r_rect, in_l_rect = mediapipe_hand.hands_estimation(image, right_box_x, right_box_y, left_box_x, left_box_y)
# 依照左右手位置的條件來畫框
image = draw_rect_box(image, in_r_rect, in_l_rect)
# 在一般模式下,將這兒設為 True,以便下一次開始遊戲時,要作一些事
is_first_in_game_mode = True
# 若左手此時都在框中,就進入遊戲模式
is_game_mode = in_r_rect and in_l_rect
if is_game_mode:
play_loop_count = 0
total_score = 0
# 存放到 Queue 中,若 Queue 已滿,就先取出一個舊的,加入一個新的
if game_movie_queue.full():
game_movie_queue.get_nowait()
if not is_game_mode:
game_movie_queue.put(image)
# 每 image 錄影
if out_movie is not None:
out_movie.write(image)
cv2.imshow('手忙腳亂', cv2.resize(image, (1024, 768), interpolation=cv2.INTER_AREA))
# 需有 waitKey,畫面才會顯示內容
press_key = cv2.waitKey(5) & 0xFF
# 保留按下 ESC 結束整個畫面的功能
if press_key == 27:
break