程式設計師買啥遊戲機,自己動手做一個體感小遊戲
摘要:結合一個仿製的簡易Flappy Bird遊戲,ModelBox體感小遊戲就這樣誕生了。
本文分享自華為雲社區《ModelBox開發案例 – 體感小遊戲》,作者:菊廠飛戈。
前段時間,小魚老師在AI說發布了文章 ModelBox推理真的高效嗎,裡面介紹了雙階段單人人體關鍵點檢測案例,運行速度超快:使用原生的ONNXRuntime API做開發,可以達到36fps;而ModelBox版本(推理框架同樣是ONNXRuntime),更是達到了接近80fps!
於是乎,筆者產生了一個大膽的想法:這麼快的人體關鍵點檢測應用,不用來跑遊戲可惜了呀!經過一段時間的開發調試,結合一個仿製的簡易Flappy Bird遊戲,ModelBox體感小遊戲誕生了:

玩家通過上下擺動雙臂做出「扇動翅膀」的動作,阻止小鳥下落,躲避畫面中的「狼柱」;如果小鳥不小心碰到了「狼」或者觸碰到畫面邊緣,遊戲停止,然後會重新開始。畫面左上方顯示的是玩家存活的時長。
本案例使用的是Windows版本的 ModelBox SDK,如果對此還不熟悉,請先閱讀 ModelBox端雲協同AI開發套件(Windows)開發環境篇,如果對 ModelBox AI應用開發感興趣,歡迎參加我們的 ModelBox實戰營。
工程結構
本案例是在小魚老師的 single_human_pose 應用基礎上修改而來,案例所需資源(程式碼、模型、測試數據等)均可從obs桶下載。工程目錄與原始版本基本一致,下面列出其中不一樣的地方:
single_human_pose ├─data │ ├─game // 體感遊戲資源目錄 │ ├─icon // 體感遊戲所需的圖標資源 │ ├─src // 體感遊戲源程式碼,可獨立運行 │ └─dance_120fps.mp4 // 測試影片 ├─etc │ └─flowunit // 功能單元目錄 │ ├─draw_pose // 關鍵點繪製功能單元 │ ├─draw_pose.py // 關鍵點繪製功能單元入口文件 │ ├─draw_pose.toml // 關鍵點繪製功能單元配置文件 │ ├─draw_utils.py // 其他功能函數存放文件 │ ├─flappy.py // Flappy Bird核心邏輯 │ ├─smooth.py // 平滑演算法,使關鍵點更穩定 │ └─vector.py // 平面坐標系中點的運算 │ ├─... // 其他功能單元 ├─graph │ ├─single_human_pose.toml // 默認的技能流程圖 │ └─single_human_pose_camera.toml // 使用攝影機運行的技能流程圖 ├─... └─build_project.sh
Flappy Bird
本案例中遊戲相關的資源和程式碼在 data/game 目錄下,我們可以直接執行其中 src 下的 main.py 文件,得到一個使用鍵盤控制的 Flappy Bird 遊戲。main.py 文件中的核心函數內容如下:
def run(): # 初始化遊戲 snake = Snake() # 貪吃蛇 flappy = Flappy() # Flappy Bird pacman = Pacman() # 吃豆人 # 初始化攝影機,參數0表示設備的第一個攝影機 cap = cv2.VideoCapture(0) # 判斷攝影機初始化是否成功 if not cap.isOpened(): print('failed to open camera 0') exit() # 設置解析度為 960 x 540 cap.set(3, 960) cap.set(4, 540) while True: # 讀取一幀影片影像,ret表示讀取是否成功 ret, frame = cap.read() # 對原始影像做高斯模糊,避免干擾到遊戲畫面 frame = cv2.GaussianBlur(frame, (0, 0), 5) # 阻塞等待鍵盤響應1ms,獲取按下的按鍵值 pressed_key = cv2.waitKey(1) & 0xFF if pressed_key == ord('q'): # 如果按下q鍵則退出遊戲 break else: # 根據按鍵類型更新遊戲畫面 # frame = snake.update_snake_keyboard(frame, pressed_key) frame = flappy.update_flappy_keyboard(frame, pressed_key) # frame = pacman.update_pacman_keyboard(frame, pressed_key) # 打開一個名為game的窗口,顯示影像 cv2.imshow('game', frame) # 釋放攝影機資源 cap.release() # 關閉所有窗口 cv2.destroyAllWindows()
可以看到,其中包含了3個小遊戲:貪吃蛇、吃豆人和Flappy Bird。遊戲介面使用OpenCV繪製,程式將打開0號攝影機,將遊戲畫面疊加在攝影機畫面上;在每幀的繪製中,程式阻塞1ms等待鍵盤響應,根據按鍵不同控制遊戲的運行:按下空格鍵將控制小鳥往上飛行一段距離(具體內容查看 src/flappy.py )。開發者可以解開另外兩個遊戲的注釋程式碼,試試它們的遊戲效果。
關鍵點繪製功能單元
Flappy Bird遊戲與人體關鍵點檢測應用的結合,完全容納在 draw_pose 功能單元中。在原始的 single_human_pose 應用里,這個功能單元只是將檢測到的關鍵點數據繪製到畫面中;本應用中,在得到人體關鍵點數據後,又計算了雙臂與身體的夾角,如果檢測到「扇動翅膀」的動作,則控制小鳥往上飛行一段距離。遊戲畫面與高斯模糊後的人體關鍵點畫面疊加在一起顯示,既能看到AI應用的效果,也不至於干擾到遊戲畫面的顯示。
def open(self, config): ... # 使用圖標資源初始化Flappy Bird遊戲控制示例 icon_path = config.get_string("icon_path", ".") self.flappy = Flappy(icon_path) return modelbox.Status.StatusCode.STATUS_SUCCESS def process(self, data_context): ... for image, hand_pose in zip(in_image, in_feat): ... # 獲取上一功能單元輸出的人體關鍵點數據 pose_data = np.array(hand_pose.as_object(), copy=False) pose_data = pose_data.reshape((self.kps, 3)) # 計算雙臂與身體的夾角 arm_angles = get_arm_angles(bbox, pose_data, self.keypoints_smooth) ... # 在攝影機畫面中畫出主要的人體關節,並作高斯模糊 draw_pose(out_img, bbox, pose_data, self.score_thre) out_img = cv2.GaussianBlur(out_img, (0, 0), 5) # 根據雙臂動作控制遊戲畫面更新,疊加到攝影機畫面中做展示 out_img, alive = self.flappy.update_flappy_pose(out_img, arm_angles, fps) ... return modelbox.Status.StatusCode.STATUS_SUCCESS
camera流程圖
遊戲的運行需要實時的攝影機畫面,因此本案例增加了使用PC自帶或者外接的USB攝影機作為輸入源的流程圖,對應文件為 single_human_pose_camera.toml,其中的流程圖描述 graphconf 內容如下:
graphconf = """digraph single_human_pose { node [shape=Mrecord] queue_size = 1 batch_size = 1 input1[type=input,flowunit=input,device=cpu,deviceid=0] data_source_parser[type=flowunit, flowunit=data_source_parser, device=cpu, deviceid=0] local_camera[type=flowunit, flowunit=local_camera, device=cpu, deviceid=0, pix_fmt=bgr, cam_width=960, cam_height=540] det_pre[type=flowunit, flowunit=det_pre, device=cpu, deviceid=0] color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid=0] normalize[type=flowunit flowunit=normalize device=cpu deviceid=0 standard_deviation_inverse="0.003921568627451, 0.003921568627451, 0.003921568627451"] det_human[type=flowunit, flowunit=det_human, device=cpu, deviceid=0, batch_size=1] det_post[type=flowunit, flowunit=det_post, device=cpu, deviceid=0] object_tracker[type=flowunit, flowunit=object_tracker, device=cpu, deviceid=0] expand_box[type=flowunit, flowunit=expand_box, device=cpu, deviceid=0] image_resize[type=flowunit flowunit=resize device=cpu deviceid="0" image_width=192, image_height=256] color_transpose2[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid=0] mean[type=flowunit flowunit=mean device=cpu deviceid="0" mean="116.28,103.53,123.68"] normalize2[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="0.0175070,0.01742919,0.01712475"] det_pose[type=flowunit, flowunit=det_pose, device=cpu, deviceid=0, batch_size=1] pose_post[type=flowunit, flowunit=pose_post, device=cpu, deviceid=0] draw_pose[type=flowunit, flowunit=draw_pose, device=cpu, deviceid=0] video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0] input1 -> data_source_parser:in_data data_source_parser:out_video_url -> local_camera:in_camera_packet local_camera:out_camera_frame -> det_pre:in_image det_pre:resized_image -> color_transpose:in_image color_transpose:out_image -> normalize:in_data normalize:out_data -> det_human:input det_human:output1 -> det_post:in_feat1 det_human:output2 -> det_post:in_feat2 det_human:output3 -> det_post:in_feat3 det_pre:out_image -> det_post:in_image det_post:has_human -> object_tracker:in_image object_tracker:out_image -> expand_box:in_image expand_box:out_image -> image_resize:in_image image_resize:out_image -> color_transpose2:in_image color_transpose2:out_image -> mean:in_data mean:out_data -> normalize2:in_data normalize2:out_data -> det_pose:image det_pose:heatmap -> pose_post:in_feat pose_post:out_data -> draw_pose:in_feat object_tracker:out_image -> draw_pose:in_image draw_pose:out_image -> video_out:in_video_frame det_post:no_human -> video_out:in_video_frame }"""
與 single_human_pose.toml 相比,這個流程圖使用 local_camera 替換了 video_demuxer 和 video_decoder 功能單元,其他部分是一致的。
打開工程目錄下bin/mock_task.toml文件,修改其中的任務輸入和任務輸出配置為如下內容:
[input] type = "url" url = "0" # 表示0號攝影機,即PC自帶攝影機,若PC無攝影機需外接USB攝影機 [output] type = "local" url = "0:pose_game" # 表示名為```pose_game```的本地窗口
即使用編號為0的攝影機(默認為PC自帶的攝影機),輸出畫面顯示到名為pose_game的本地螢幕窗口中。
執行bin/main.bat camera運行應用,就可以開始遊戲了:
改造自己的體感小遊戲
本案例展示了 ModelBox AI應用與遊戲的結合,開發者可以調整其中的遊戲邏輯控制遊戲的難易程度,如小鳥降落/飛升的速度、狼柱的出現頻率與位置等;還可以改成使用其他動作或者手勢控制小鳥飛行,如殭屍跳、開合跳等;另外,案例中提供了貪吃蛇、吃豆人這兩款遊戲源碼,開發者也可以將它們改造成體感小遊戲。
行動起來,去享受AI與遊戲的樂趣吧~~
11月3號晚19點,將進行AI養豬 實時看護豬的健康直播,華為雲AI發燒友在線帶你學習ModelBox框架,快速AI應用,實現AI監測豬的健康狀態。
參與直播互動,有機會贏取華為自拍桿、雷柏機械鍵盤、ModelArts書籍等多重好禮,還等什麼,馬上報名吧!