愤怒的小鸟,体感大改造

如果你喜欢这个项目欢迎右上角给我一个爱心,当然fork一起来玩并提出需求和反馈更是对我的大力支持~

项目对应的github链接在这,欢迎给我star和fork哦谢谢你的支持

林暗草惊风,将军夜引弓。平明寻白羽,没在石棱中。

弓是抛射兵器中最古老的一种弹射武器。弓箭作为远射兵器,在春秋战国时期应用相当普遍,也被列为兵器之首。

而与之对应的“射”作为一种技艺是公卿大夫必须通晓的“六艺”之一,不仅在国君会盟、宴会上被视为一种礼仪,而且在民间风俗中也以它为礼节。

从传说中的后羿,到李广吕布黄忠,历史上有很多用弓好手。在现在奥林匹克运动会上,仍有射箭比赛这个项目。

除此之外,在影视剧中,也有射箭的相关的描写,譬如名字就点题的射雕英雄传中,郭靖、哲别都是神射手,指环王中的精灵王子莱格拉斯也是弓箭手。

在ACG圈子内,流传着一句话,自古弓兵多挂逼。意思是弓兵都是开挂的,除去红A和金闪闪不说,犬夜叉中的桔梗,圣斗士中的射手座,数不胜数。

游戏里更是如此,大多数的古风或是中世纪风的游戏,都摆脱不了弓箭手这个职业。塞尔达中林克用弓不要太帅。

今天我们也来练习一下射箭,我们射个大点的,射个…猪吧。

我最近改写了个游戏。其实就是给愤怒的小鸟增加上了动作识别以及一个分类器。其实,愤怒的小鸟中用的弹弓也是属于弓的一种。

在我魔改的游戏中,我们同样要做拉弓的姿势,才能射出我们的…小鸟

这里的动作识别,其实就是人体关键点检测。

通过评估Paddlehub以及PaddleDetection中的三个关键点检测模型,我最终选择了PaddleDetection中的hrnet的模型。最终的帧率大概每秒17帧这个样子。

为了方便使用,我把要使用的这个hrnet库又简单的封装了一下,放在了mykeypointdetector文件夹中,方便后续的调用。大部分都是PaddleDetection中提供的deploy里的源码,只是我简化掉了了一下我不会触发的条件。

# 这部分就是子线程的代码,把关键点检测和分类都放在这里。然后也可以关闭debug,可能会更快一丢丢丢丢丢。

class MyThread(Thread):
    def __init__(self, video_file = "mykeypointdetector/mine.mp4", debug=True):
        super(MyThread, self).__init__()
        
        self.md = MyDetector("mykeypointdetector/output_inference/hrnet_w32_384x288/", True)
        self.video_file = video_file
        self.debug = debug
        self.capture = cv2.VideoCapture(self.video_file)
        self.h = self.capture.get(4)
        self.w = self.capture.get(3)
        self.FOC = FOClassifer()

    def run(self):
        global results
        global foresults
        global mouse_pressed
        htop = None
        hbottom = None
        hleft = None
        hright = None
        # print("enter detector process", 4)
        radius = 70
        while (1):
            ret, frame = self.capture.read()
            if not ret:
                break
            frame = cv2.flip(frame, 1)

            tresults = self.md.detector.predict(frame)
            
            if tresults['keypoint'][0][0][10][2] > 0.5 and tresults['keypoint'][0][0][8][2] > 0.5:
                
                wx = int(tresults['keypoint'][0][0][10][0])
                wy = int(tresults['keypoint'][0][0][10][1])
                # print(cx , cy)

                ex = int(tresults['keypoint'][0][0][8][0])
                ey = int(tresults['keypoint'][0][0][8][1])

                cx = wx * 2 - ex
                cy = wy * 2 - ey

                cx = int((cx + wx) / 2)
                cy = int((cy + wy) / 2)

                if cx < 0:
                    cx = 0
                if cx >= self.w:
                    cx = self.w - 1
                if cy < 0:
                    cy = 0
                if cy >= self.h:
                    cy = self.h - 1
                
                htop = int(cy - radius) if cy - radius > 0 else 0
                hbottom = int(cy + radius) if cy + radius < self.h else int(self.h-1)
                hleft = int(cx - radius) if cx - radius > 0 else 0
                hright = int(cx + radius) if cx + radius < self.w else int(self.w - 1)
                # print(htop, hbottom, hleft, hright)
                if mouse_pressed:
                    tfo = self.FOC.classifer(frame[htop:hbottom, hleft:hright])
                else:
                    tfo = None
            else:
                tfo = None
            if tresults['keypoint'][0][0][10][2] > 0.5 and tresults['keypoint'][0][0][9][2] > 0.5:
                lock.acquire()
                results = tresults
                foresults = tfo
                lock.release()
            else:
                lock.acquire()
                results = None
                foresults = None
                lock.release()
            '''watch image'''
            if self.debug:
                if tresults is not None:
                    for key in tresults['keypoint'][0][0]:
                        cv2.circle(frame,(int(key[0]), int(key[1])), 5, (0,0,255), -1)
                if tfo is not None:
                    if tfo is True:
                        cv2.rectangle(frame, (hleft, htop), (hright, hbottom), (0, 255, 0), 2)
                    else:
                        cv2.rectangle(frame, (hleft, htop), (hright, hbottom), (255, 0, 0), 2)
                cv2.imshow("test", frame)
                cv2.waitKey(1)

一、首先,我们要放上我们的弓箭

摄像头检测到我们两只手很靠近的时候,就会把这个动作标记为我们要搭箭的状态,也就是在原游戏中通过在弹弓附近点击触发的我们要开始拉弹弓的事件。


在代码层级的时候,我们先取得双肩的像素坐标以及两个手腕的像素坐标,这样可以计算出双肩距离L1和双腕距离L2,当L2<L1的时候,我们就认为出发了点击事件。

# 对应源代码的698行左右
# 这里的pose为none是避免了关键点检测模块还没有更新,但是游戏线程已经在运行的情况
# 这个在前面的地方有一个线程锁,用来保证数据是最新的,正确的

if((pose is not None) and distance(wristl[0], wristl[1], wristr[0], wristr[1]) < distance(shoulderl[0], shoulderl[1], shoulderr[0], shoulderr[1])):
    mouse_pressed = True

二、接着,我们要拉弓

对,就是拉弓。然后小鸟也会对应的被拉到相应的位置。这里检测的仍然是我们的手腕,需要记录的是两个手腕的距离和相对角度。映射到游戏中,手腕的距离都是拉弓的力度,手腕的相对角度就是拉弹弓的方向。


在原游戏中有一些常量,譬如弹弓上两个绳子的结点是固定的,原游戏中,这个发射小鸟的力度和角度是通过鼠标坐标与弹弓一个结点的距离和角度决定的。

在我们更改之后,这个距离和角度则改为我们两只手腕的距离和角度决定。

因为这里需要把我们的手的距离映射到游戏中的[0,最大距离]之间,所以我们需要一个值,作为一个基准,这里我使用的也是关键点上的相对距离。

# 原游戏179行
# y_mouse, x_mouse是鼠标位置,sling_y, sling_x是弹弓的emmmm,绑绳的那个结点
dy = y_mouse - sling_y
dx = x_mouse - sling_x
if dx == 0:
    dx = 0.00000000000001
angle = math.atan((float(dy))/dx)

# 改写后259行
# Angle of impulse
# x是横的,y是竖的
# 0是横, 1是竖的
# sl左腕,sr右腕
dy = sl[1] - sr[1]
dx = sl[0] - sr[0]
if dx == 0:
    dx = 0.00000000000001
angle = math.atan((float(dy))/dx)

三、最后,放开我们的左手,go!小鸟发射!

这里是一个分类模型,检测手是握拳还是手掌,握拳则为拉着弓,手掌则为放箭来了。


可以来看这个项目 数据增广+手势识别[Paddlehub+PaddleX]

# 非常简陋的封装了paddlex导出的分类模型。这部分会和关键点检测模型一起放在子线程中。
# 在连续几帧都检测到了撒放的手势后,则会把结果返回给我们的关键点检测中,并通过参数传递到主线程中去。
class FOClassifer():
    def __init__(self):
        super().__init__()
        self.model = pdx.deploy.Predictor('inference_model', use_gpu=True)
        self.count = 0

    def classifer(self, frame):
        result = self.model.predict(frame.astype('float32'))[0]['category_id']
        if result == 0:
            self.count += 1
            # print("-tforesults", 0)
        else:
            self.count = 0
        # rint("---tforesults count ", self.count)
        if self.count > 2:
            self.count = 0
            return True
        else:
            return False

运行

在src目录下,运行mymain_thread.py

当然,现在在AiStudio是没办法运行了,大家在本地玩一下吧~

总结

项目其实尝试了很久,原版的游戏bug很多,也让我头疼了一段时间,好在现在有个能平稳运行的版本

现在的关键点检测模型还是太慢了,据说PaddleDetection出了更快的模型,大家可以改一下诗诗,估计效果会好很多~

个人简介

百度飞桨开发者技术专家 PPDE

飞桨上海领航团团长

百度飞桨官方帮帮团、答疑团成员

国立清华大学18届硕士

以前不懂事,现在只想搞钱~欢迎一起搞哈哈哈

我在AI Studio上获得至尊等级,点亮9个徽章,来互关呀!!!

https://aistudio.baidu.com/aistudio/personalcenter/thirdview/311006

B站ID: 玖尾妖熊

其他趣味项目:

Padoodle: 使用人体关键点检测让涂鸦小人动起来
利用PaddleHub制作"王大陆"滤镜
利用Paddlehub制作端午节体感小游戏
熊猫头表情生成器[Wechaty+Paddlehub]
如何变身超级赛亚人(一)–帅气的发型
【AI创造营】是极客就坚持一百秒?
在Aistudio,每个人都可以是影流之主[飞桨PaddleSeg]
愣着干嘛?快来使用DQN划船啊
利用PaddleSeg偷天换日~
Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐