本项目使用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
即可开始游戏
手掌代表向下走
大拇指向左代表向左走
空白表示不动
大拇指向右表示向右走
大拇指向上表示向上走
灰度数据采集基于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’键,将阈值化图像存储到指定的路径下。
数据读取与预处理
本项目使用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%的准确率。
本项目包含3个精灵类,分别为:墙类、食物类、角色类。
墙类:高(height
)、宽(width
)、横坐标(rect.x
)、纵坐标(rect.y
)、填充色(color
);
食物类:高、宽、横坐标、纵坐标、填充与透明色(bg_color
)、图形颜色(color
);
角色类:
prev_x
、prev_y
)、图片路径、运动轨迹(tracks
)、基础、实际移动速度(距离speed
、base_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
实例化墙:通过精灵组与墙类实现墙的构建;
实例化门:通过精灵组与墙类实现门的构建;
实例化食物:通过精灵组与食物类实现食物的生成,对于和墙、门、玩家、敌人冲突的食物,通过碰撞检测进行覆盖;
# 精灵碰撞检测
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
实例化角色:
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)
导入一些必要的包:
定义一些必要的参数:颜色、图片、背景音乐、字体;
初始化游戏界面:加载游戏框体、游戏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
实现高斯模糊处理,将处理后的灰度图借助transform
与torch
进行向量转换,借助预测函数实现手势识别预测;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审核中~~
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。