5 Star 1 Fork 0

jiangwenduo / 吃豆人(手势控制版)

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

吃豆人(手势控制版)

介绍

本项目使用Pygame复刻经典游戏PacMan,使用ResNet18进行手势识别

使用教程

  • 数据集制作

    在使用本项目前需要训练ResNet的权重,用户可通过使用DataCollect中的collect_gray.py进行灰度数据的采集,采集完成数据后运行trans_files.py进行数据集划分,从上述过程采集的数据的每类样本中随机抽取10%用作测试集,剩余90%用作训练集。最后运行get_data_list.py生成训练/测试数据列表。

  • 手势识别模型训练

    完成数据集制作后,需要使用制作的数据集进行训练,运行Resnet中的Resnet180gray.ipynb进行手势识别模型的训练,训练好的模型会保存在同级目录下,文件名为model.pth

  • 测试

    完成训练后,用户可运行predict.iptnb使用同级目录下提供的测试和数据对训练好的模型进行测试

  • 游玩

    完成上述工作后,运行game.py即可开始游戏

数据集说明

手掌代表向下走down

大拇指向左代表向左走left

空白表示不动pause

大拇指向右表示向右走right

大拇指向上表示向上走up

数据采集代码详解

灰度数据采集基于opencv进行实现,首先读取视频流,抓取视频中的每一帧,将帧进行镜像反转,使用run_avg方法将当前帧进行高斯模糊处理:

def run_avg(image, aWeight):
    global bg
    if bg is None:
        bg = image.copy().astype('float')
        return

    cv2.accumulateWeighted(image, bg, aWeight)

再使用segment方法获取手部区域:

def segment(image, threshold=25):
    global bg
    diff = cv2.absdiff(bg.astype('uint8'), image)

    thresholded = cv2.threshold(diff,
                                threshold,
                                255,
                                cv2.THRESH_BINARY)[1]

    (cnts, _) = cv2.findContours(thresholded.copy(),
                                 cv2.RETR_EXTERNAL,
                                 cv2.CHAIN_APPROX_SIMPLE)

    if len(cnts) == 0:
        return
    else:
        segmented = max(cnts, key=cv2.contourArea)
        return (thresholded, segmented)

segement方法通过比较当前帧图像与北京图像之间的差异,并根据阈值化和轮廓检测找到手部区域的轮廓,返回阈值化图像和手部轮廓。

程序监听键盘操作,用户按下‘s’键,将阈值化图像存储到指定的路径下。

Resnet18训练详解

  • 数据读取与预处理

    本项目使用torchvision.datasets中的Image_Floder方法与数据集制作阶段中得到的训练/测试数据自动进行数据读取

    transform = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize([ 0.5, 0.5], [ 0.5, 0.5])
    
    ])

    使用transforms模块创建数据转换管道,其中transforms.Compose函数将多个图像转换操作组合成一个序列,按顺序应用于输入的图像。

    transforms.Resize((224, 224)):将输入图像的尺寸调整为(224, 224)。这通常是为了适应特定的神经网络模型对输入图像尺寸的要求,例如常用的预训练模型如ResNet、VGG等。

    transforms.ToTensor():将图像数据转换为torch.Tensor类型。PyTorch中的神经网络处理的输入数据类型通常是torch.Tensor。这个转换操作会将图像数据从PIL图像对象或NumPy数组转换为张量,并将像素值缩放到范围[0, 1]之间。

    transforms.Normalize([0.5, 0.5], [0.5, 0.5]):对输入的图像张量进行标准化。这个操作会将每个通道的像素值减去0.5,并除以0.5,从而将像素值归一化到均值为0、标准差为1的范围。这种标准化有助于提高神经网络的训练和收敛性能。

  • 损失函数与优化器

    使用交叉熵损失函数作为损失函数

    优化器使用暴力的Adam优化器

    def adjust_learning_rate(optimizer, epoch):
        modellrnew = modellr * (0.1 ** (epoch // 5))
        print("lr:", modellrnew)
        for param_group in optimizer.param_groups:
            param_group['lr'] = modellrnew

    使用上面的adjust_learning_rate方法,随着训练轮数的增加降低学习率

  • 训练与评估结果

    本项目中各类样本采集约45张,初始学习率为1e-4,batch大小为64,在单卡P100上训练20轮,在测试集上最高取得97%的准确率。

游戏代码详解

精灵类(Sprites.py)

本项目包含3个精灵类,分别为:墙类、食物类、角色类。

  • 墙类:高(height)、宽(width)、横坐标(rect.x)、纵坐标(rect.y)、填充色(color);

  • 食物类:高、宽、横坐标、纵坐标、填充与透明色(bg_color)、图形颜色(color);

  • 角色类:

    • 构造函数:横坐标、纵坐标、前一时刻的横纵坐标(prev_xprev_y)、图片路径、运动轨迹(tracks)、基础、实际移动速度(距离speedbase_speed);
    • 移动准备函数(changeSpeed):移动时改变图片方向,返回移动距离;
    direction[1] < 0:#向上
       self.image = pygame.transform.rotate(self.base_image, 90)
    self.speed = [direction[0] * self.base_speed[0], direction[1] * self.base_speed[1]]
    • 更新位置函数(update):更新前一时刻位置参数,如果没撞到门或者墙则改变位置参数、否则不变;
    is_collide = pygame.sprite.spritecollide(self, wall_sprites, False)
    # print('asd',is_collide)
    if gate_sprites is not None:#门没有消失
       if not is_collide: #没撞到墙
          is_collide = pygame.sprite.spritecollide(self, gate_sprites, False)
          print(is_collide)     #是否撞到门
    if is_collide:#保持原位
       self.rect.left = x_prev
       self.rect.top = y_prev

关卡类(Levlels.py)

  • 实例化墙:通过精灵组与墙类实现墙的构建;

  • 实例化门:通过精灵组与墙类实现门的构建;

  • 实例化食物:通过精灵组与食物类实现食物的生成,对于和墙、门、玩家、敌人冲突的食物,通过碰撞检测进行覆盖;

    # 精灵碰撞检测
    is_collide = pygame.sprite.spritecollide(food, self.wall_sprites, False)
    if is_collide:
       continue
    is_collide = pygame.sprite.spritecollide(food, self.hero_sprites, False)
    if is_collide:
       continue
  • 实例化角色:

    • 新建玩家角色精灵组,借助角色类实现玩家角色的生成;
    • 新建敌人角色精灵组,借助角色类实现AI敌人的生成,同时为不同的敌人确认不同的移动轨迹;
    role_name == 'Clyde':
       player = Player(319, 259, each)
       player.is_move = True
       player.tracks = [[-1, 0, 2], [0, -0.5, 4], [0.5, 0, a], [0, 0.5, 7], [-0.5, 0, 11], [0, -0.5, 7],
                    [-0.5, 0, 3], [0, 0.5, 7], [-0.5, 0, 7], [0, 0.5, b], [0.5, 0, 15], [0, -0.5, 3],
                    [-0.5, 0, c], [0, -0.5, 7], [0.5, 0, 3], [0, -0.5, d], [0.5, 0, 9]]
       self.ghost_sprites.add(player)

游戏主体(game.py)

  • 导入一些必要的包:

    • 游戏:os、sys、pygame、Levels;
    • 手势识别:cv2、imutils、torch、torchvision.transforms;
  • 定义一些必要的参数:颜色、图片、背景音乐、字体;

  • 初始化游戏界面:加载游戏框体、游戏logo、游戏标题;

  • 文字显示函数:

    • 提示游戏结束文字内容;
    • 重启或退出游戏:
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_RETURN:
            if is_clearance:
                if not flag:
                    return
                else:
                    main(initialize())
            else:
                main(initialize())
        elif event.key == pygame.K_ESCAPE:
            sys.exit()
            pygame.quit()
  • 游戏运行函数:

    • 初始化墙、门、食物、玩家、AI敌人精灵组;

    • 建立手势识别函数:

      • 通过cv2.cvtColor将手势图转为灰度图,借助cv2.GaussianBlur实现高斯模糊处理,将处理后的灰度图借助transformtorch进行向量转换,借助预测函数实现手势识别预测;
      gray_image = thresholded
      input_tensor = transform(gray_image)
      input_batch = input_tensor.unsqueeze(0)  # 添加批次维度
      with torch.no_grad():
          output = model(input_batch)
      _, predicted_idx = torch.max(output, 1)
      predicted_label = predicted_idx.item()
      • 通过手势返回的结果进行玩家角色的移动

        if classes[predicted_label] == 'left':
            for hero in hero_sprites:
                hero.changeSpeed([-1, 0])
                hero.is_move = True
        for hero in hero_sprites:
            hero.update(wall_sprites, gate_sprites)
    • AI敌人移动:通过判断当前位置所在的路径是否比规定路径长

      • 如果没有则按规定路径一直往前走
      • 如果有则换下一条路径继续走
  • 主函数:

    加载游戏背景音乐、游戏字体、运行游戏,并根据食物数量进行游戏胜利的判断。

    if ghost.tracks_loc[1] < ghost.tracks[ghost.tracks_loc[0]][2]:
        ghost.changeSpeed(ghost.tracks[ghost.tracks_loc[0]][0: 2])
        ghost.tracks_loc[1] += 1
    else:
        if ghost.tracks_loc[0] < len(ghost.tracks) - 1:
            ghost.tracks_loc[0] += 1    #走下一段路
        elif ghost.role_name == 'Clyde':    #特殊处理,从2号继续
            ghost.tracks_loc[0] = 2
        else:
            ghost.tracks_loc[0] = 0     #遍历完一般重新遍历
        ghost.changeSpeed(ghost.tracks[ghost.tracks_loc[0]][0: 2])
        ghost.tracks_loc[1] = 0

演示视频

bilibili审核中~~

空文件

简介

吃豆人(手势控制版) 展开 收起
Python 等 2 种语言
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Python
1
https://gitee.com/ginger-winner/pac-man.git
git@gitee.com:ginger-winner/pac-man.git
ginger-winner
pac-man
吃豆人(手势控制版)
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891