项目简介
近年来,随着虚拟现实技术和计算机图形学技术的迅猛发展,越来越多的体感游戏在市场上出现并受到欢迎。要让体感游戏具备良好的表现,就需要使用大量的传感器,甚至需要使用高性能的计算机和图形处理器。这不仅会增加游戏的成本,还会影响游戏的流畅性和操作体验。因此,如何在不使用传感器的情况下,实现高性能的体感游戏开发成为了一个颇具难度的问题。
为了解决这个问题,本文提出了基于关键点检测模型和摄像头的体感游戏开发方案。其中,PP-TinyPose 模型是飞桨推出的人体检测算法,它可以快速、准确地检测出人体关键点,从而实现人体的实时跟踪和动作捕捉。而通过飞桨推理部署工具 FastDeploy,可以将 PP-TinyPose 模型与推理引擎相结合,实现高效的推理部署,从而让游戏可以在仅使用 USB 摄像头的情况下,流畅地运行在一般电脑上。
https://aistudio.baidu.com/aistudio/projectdetail/4672452
https://aistudio.baidu.com/aistudio/projectdetail/5686677
本文综合了上述项目的开发经验,介绍了一个简易的体感游戏开发框架,可以在该框架的基础上轻松自制基于摄像头和关键点检测模型的体感小游戏。
Liyulingyue/PaddleGames: Some games based on paddlepaddle (github.com)
PP-TinyPose 模型是飞桨目标检测套件 PaddleDetection 针对移动端设备优化的实时关键点检测模型,其可以流畅地在移动端设备上执行多人姿态估计任务。结合轻量化检测网络 PP-PicoDet 能够快速高效地完成检测任务,并应用在手机等计算能力有限的边缘设备上。
https://github.com/PaddlePaddle/PaddleDetection
FastDeploy 是飞桨推出的一款全场景、易用灵活、极致高效的AI推理部署工具,提供大量的开箱即用的部署体验。目前,FastDeploy 支持的模型包括了物体检测、字符识别、NLP、Stable Diffusion 文图生成等。内置的推理后端包含了 TensorRT、OpenVINO 等。
使用 FastDep loy 调用 PP-TinyPose 的优势:
通过下述三行代码即可完成推理过程,无需对数据进行预处理。
import fastdeployimport cv2 model=fastdeploy.vision.keypointdetection.PPTinyPose('PP_TinyPose_128x96_infer/model.pdmodel' ,'PP_TinyPose_128x96_infer/model.pdiparams' ,'PP_TinyPose_128x96_infer/infer_cfg.yml' ) img = cv2.imread('test.jpg' ) result = model.predict(img)
PaddleDetection 仓库已提供了性能优秀的的预训练模型。在此基础上, FastDeploy 不仅提供了开箱即用的 API ,对运算过程进行了一定的优化,从而提高了模型推理的实时性,有利于在线解算。
01 基于 PyQt5 的通用简易体感游戏开发框架
一个简单的游戏框架可以没有任何关卡,甚至没有得分,只负责游戏的环境运营即可。例如贪吃蛇游戏,可以不设关卡,一直进行,直到蛇的身体充斥整个屏幕。这个最简单的逻辑结构一共需要准备两个部分的内容。
如上图(右)所示,主要负责初始化页面信息,周期性地调用摄像头数据,并且推理,将推理结果传给后端,并获取当前的游戏画面。
如上图(左)所示,可以声明为一个类,在游戏开始时会创建这个类,并且周期性地调用更新函数,根据关键点信息更新游戏的变量,并且根据当前的游戏数据,绘制游戏画面。
这里分开来表述只是为了后续维护方便,实际上完全可以混在一起。并且上图的表述更为偏向人而非代码的逻辑,所以会和后续的代码介绍有些出入。下面分别对游戏开发框架的前后端代码进行介绍。
游戏管理器初始化:用于创建游戏变量
设备初始化:开启摄像头
class Window (QWidget ): def __init__ (self ) : super ().__init__ () self .game_obj = GameObject() self .keypoints = None self .initModel() self .initCamera() self .initClock() self .initUI() def initUI (self ) : grid = QGridLayout() self .setLayout(grid) self .Game_Box = QLabel() # 定义显示视频的Label self .Game_Box.setFixedSize(500 , 500 ) grid.addWidget(self .Game_Box, 0 , 0 , 20 , 20 ) self .Game_Box.setMouseTracking(True) self .Pred_Box = QLabel() # 定义显示视频的Label self .Pred_Box.setFixedSize(500 , 500 ) grid.addWidget(self .Pred_Box, 0 , 20 , 20 , 20 ) self .setWindowTitle('test' ) self .show() def initClock (self ) : # 通过定时器读取数据 self .flush_clock = QTimer() # 定义定时器,用于控制显示视频的帧率 self .flush_clock.start(30 ) # 定时器开始计时30ms,结果是每过30ms从摄像头中取一帧显示 self .flush_clock.timeout.connect(self .updata_frame) # 若定时器结束,show_frame() def initCamera (self ) : # 开启视频通道 self .camera_id = 0 # 为0时表示视频流来自摄像头 self .camera = cv2.VideoCapture() # 视频流 self .camera.open(self .camera_id) def initModel (self ) : self .model = fastdeploy.vision.keypointdetection.PPTinyPose('../../Models/PP_TinyPose_128x96_infer/model.pdmodel' ,'../../Models/PP_TinyPose_128x96_infer/model.pdiparams' ,'../../Models/PP_TinyPose_128x96_infer/infer_cfg.yml' )
需要注意的是,这里为了拆解功能,把更新数据和展示画面分开了。代码如下:
def inferModel (self ) : # read pic from camera _ , img = self .camera.read() # 从视频流中读取 img = cv2.flip(img, 1 ) # 摄像头画面反转 img2 = cv2.resize(img, (500 , 500 )) # 把读到的帧的大小重新设置为 640x480 showPic = QImage(img2, img2.shape[1 ], img2.shape[0 ], QImage.Format_BGR888) self .Pred_Box.setPixmap(QPixmap.fromImage(showPic)) try: result = self .model.predict(img) self .keypoints = result.keypoints showPic = QImage(img, img.shape[1 ], img.shape[0 ], QImage.Format_BGR888) self .Pred_Box.setPixmap(QPixmap.fromImage(showPic)) except: pass def updata_frame (self ) : self .inferModel() # infer and show # update balance self .game_obj.update(self .keypoints) # 绘制游戏窗口 img = self .game_obj.draw_canvas() showPic = QImage(img, 500 , 500 , QImage.Format_BGR888) self .Game_Box.setPixmap(QPixmap.fromImage(showPic)) # 游戏结束 state, score = self .game_obj.get_game_state() if state: # 游戏结束 QMessageBox.information(self , "Oops!" , "游戏结束!\n您的分数是" + str(score), QMessageBox.Yes) self .game_obj.__init__ ()
后端主要分为两个部分,初始化和更新信息。初始化内容由游戏信息决定,比如我们当前想要写一个能够随着手部移动而移动的游戏,则如下所示,只需要定义 self.x、self.y、self.score 用于保存坐标点信息和得分即可。
class GameObject (object ): def __init__ (self ) : self .x = 100 self .y = 100 self .score = 0
信息更新阶段也是随着游戏内容决定的,例如对于一个能够随着手部移动而移动的游戏,只需要每个周期根据传入的 keypoints 更新对应的坐标值即可。方便起见,可以将功能拆解,例如专门使用一个函数绘制画面或判断游戏状态。例如实例中,如果坐标值超过 250 即判断为错误。
def update (self , keypoints) :self .x = keypoints[9 ][0 ]self .y = keypoints[9 ][1 ]def get_game_state (self ) : game_status = Falseif self .x > 250 : game_status = Truereturn game_status, self .scoredef draw_canvas (self ) :# draw balance img = np.ones([500 , 500 , 3 ]).astype('uint8' ) * 255 cv2.circle(img, (int(self .x), int(self .y)), 5 , (255 , 0 , 0 ), 3 ) # draw circle return img
定义好一切后,只需要在主函数中启动 PyQt5 即可。
if __name__ == '__main__' : app = QApplication(sys.argv) ex = Window() sys.exit (app.exec_())
基于上述框架,在开发一个简易的体感小游戏时,只需要在后端代码中更新所需要逻辑即可,不需要对前端内容进行大改。
本文简单介绍如何使用飞桨推理部署工具 FastDeploy 部署飞桨目标检测套件 PaddleDetection 中的 PP-TinyPose 模型进行体感游戏开发,使用的前端框架为 PyQt5 。本文使用的示例较为简单,更多示例信息可以参考 Liyulingyue/PaddleGames: Some games based on paddlepaddle (github.com) ,其中不仅有本文介绍的 demo 的源代码,还有两个基本开发完毕的小游戏:体感贪吃蛇和体感飙车游戏。可以参考这两个项目代码定制化地开发小游戏~
同样,也欢迎大家提交代码到 Liyulingyue/PaddleGames: Some games based on paddlepaddle (github.com) ,一起改进这个 Demo 或者共同开发出更多有趣的小游戏!