# 案例六:综合项目石头剪刀布的实时识别(XEduHub+BaseNN) ## 项目说明: 组合XEdu工具集的工具完成一个综合项目非常方便,本项目使用XEduHub提取手势图像的关键点信息,再将这些关键点信息作为特征输入到一个自己搭建的全连接神经网络模型中进行训练,此步骤由BaseNN实现,最后到本地完成模型应用,实现石头剪刀布手势实时识别的综合项目。 项目地址:[https://openinnolab.org.cn/pjlab/project?id=66062a39a888634b8a1bf2ca&backpath=/pjedu/userprofile?slideKey=project#public](https://openinnolab.org.cn/pjlab/project?id=66062a39a888634b8a1bf2ca&backpath=/pjedu/userprofile?slideKey=project#public) ## 项目步骤: ### 任务一:关键点检测和简单应用 XEduHub提供了能够快速识别人手关键点的模型:pose_hand21,该模型能够识别人手上的21个关键点,如下图所示。手部关键点检测的代码可参考学习[人手关键点](https://xedu.readthedocs.io/zh-cn/master/xedu_hub/introduction.html#id44)。 ![](../images/xeduhub/new_hand.png) 首先分析检测到的手部关键点的结构: - 掌心:0; - 大拇指:1-4; - 食指:5-8; - 中指:9-12; - 无名指:12-16; - 小拇指:17-20。 经过人手关键点的模型推理,可以得到包含21对关键点坐标信息的组数,保存在keypoints变量中,通过访问数组元素,便能够轻松获取每个点的坐标信息。例如大拇指指尖4号关键点的x,y关键点坐标是keypoints[4]。 #### 1)基于规则实现手势分类 假设已经检测出了一组手部关键点,可以通过坐标点信息制定规则来区分不同的手势。例如我们写了一个计算伸展手指数量的函数,并设计判断规则通过伸展手指数量区分不同的手势。然而通过下方的代码不难看出这种方式比较麻烦,需对手指进行细致分析,同时结果可能不准确。 ```python import numpy as np # 计算两点之间的欧氏距离 def distance(p1, p2): return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) # 计算伸展手指的数量 def count_extended_fingers(keypoints, wrist_point=0, finger_tips=[4, 8, 12, 16, 20], threshold=50): #finger_tips: 每个手指尖端关键点的索引列表,默认为拇指到小指的尖端。 # 初始化伸展手指的数量为0 extended_fingers = 0 distances = [] # 存储掌心到手指尖的距离 # 获取手腕关键点的坐标 wrist = keypoints[wrist_point] # 遍历手指尖端关键点 for fingertip in finger_tips: # 计算手指尖端和掌心之间的距离 dist = distance(wrist, keypoints[fingertip]) # 计算第一节指骨到掌心之间的距离 dist1 = distance(wrist, keypoints[fingertip-1]) # 计算第二节指骨到掌心之间的距离 dist2 = distance(wrist, keypoints[fingertip-2]) distances.append(dist) # 如果是大拇指距离大于阈值则认为该手指是伸展的 if dist > threshold and fingertip==4: extended_fingers += 1 # 增加伸展手指的数量 # 如果距离大于阈值且指尖距离大于第一节指骨距离,第一节指骨距离大于第二节指骨距离则认为该手指是伸展的 elif dist > threshold and dist>dist1>dist2: extended_fingers += 1 # 增加伸展手指的数量 # 返回伸展手指的总数和每个手指的距离 return extended_fingers, distances # 假设 keypoints 是从模型获取的关键点列表 extended_fingers, finger_distances = count_extended_fingers(keypoints) threshold=5 # 判断手势 if extended_fingers == 0 or (finger_distances[0]>threshold and extended_fingers == 1): hand_gesture = "石头" elif extended_fingers == 2 and finger_distances[1] > threshold and finger_distances[2] > threshold: hand_gesture = "剪刀" elif extended_fingers == 5 and all(dist > threshold for dist in finger_distances): hand_gesture = "布" else: hand_gesture = "未知手势" print(hand_gesture) ``` #### 2)小拓展-实时人手关键点检测 还有一种简单的关键点检测应用,在多人手部关键点检测的基础上加入读取摄像头图片的代码,可以实现实时人手关键点检测。只需连接摄像头,再同时调用OpenCV库,对每一帧图像进行关键点检测,并将关键点检测的结果可视化就可以实现实时人手关键点检测。 ```python from XEdu.hub import Workflow as wf # 导入库 import cv2 cap = cv2.VideoCapture(0) det = wf(task='det_hand') # 实例化模型 model = wf(task='pose_hand21') # 实例化模型 while cap.isOpened(): ret, frame = cap.read() if not ret: break bboxs,img = det.inference(data=frame,img_type='cv2') # 进行推理 for i in bboxs: keypoints,img =model.inference(data=img,img_type='cv2',bbox=i) # 进行推理 cv2.imshow('video', img) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() ``` ### 任务二:搭建全连接神经网络训练石头剪刀布手势模型 除了任务一基于规则实现手势分类以外,还可以通过机器学习的方式,训练一个手势分类模型区分不同的手势。 #### 准备工作:准备数据 首先我们可以批量提取出所有手势图像的手势关键点数据,做一个CSV格式的关键点数据集,每行代表一张图片提出的手部关键点坐标和这张图的类别。用如下代码可以做到随拍随提取关键点,并生成关键点数据集(注意需提前安装库),可修改total值设置采集的数据条数。 ```python import csv from XEdu.hub import Workflow as wf import cv2 import os cap = cv2.VideoCapture(0) class_name=input('请输入本次想要采集的手势名称:') c_file='classes.txt' if not os.path.exists(c_file): open(c_file,'w') with open(c_file,'r') as f: lines=f.readlines() print(lines) class_index=len(lines) print('类别{',class_name,'}对应的序号是{',class_index,'}。') with open(c_file,'a') as f: s=str(class_name)+','+str(class_index)+'\n' f.write(s) pose = wf(task='pose_hand21') feature_data=[] cnt=0 from time import sleep print('3秒后开始采集数据,请做好准备....') sleep(3) total=100 # 一共采集多少条数据【可自行修改】 from tqdm import tqdm pbar=tqdm(total=total) while cnt 0: # 判断是否检测到手部关键点 pose_features = np.concatenate(keypoints_list).reshape(len(keypoints_list), -1) result = bn.inference(data=pose_features) res = bn.format_output(lang='zh') # 指定分类标签 label = ['paper', 'rock', 'scissors'] # 输出类别结果 prediction=[] for i in range(0,len(res)): index = (res[i]['预测值']) prediction.append(label[index]) print(prediction) # 在显示图像的窗口中添加预测结果的文本 cv2.putText(img, f'prediction:{prediction}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA) # 使用BGR颜色代码 cv2.imshow('video', img) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() ``` 实现效果: ![](../images/how_to_quick_start/hand.gif)