# handpose **Repository Path**: Vrtitasal2017931059/handpose ## Basic Information - **Project Name**: handpose - **Description**: No description available - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2021-03-12 - **Last Updated**: 2024-12-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于Yolo的手势识别应用 ## 前言 近年来,人机交互技术一直在不断发展,其中基于手势的人机交互方式是最自然、最直观的方式之一。手势识别人手检测等技术是这种交互方式的关键,它们一直以来都是计算机视觉领域中的研究热点。 目前有很多基于Yolo的手势识别方法。一种是自上而下的方法,这种注意机制由任务驱动,类似于由人的意识来指导。应用在手势识别上就是先检测出所有的手部关键点,再把这些点通过强连通分量分割成一个个的手部;另外一种是自下而上的方法,即先检测出手部,再对每一个手部进行关键点检测。而从上而下的视觉显著性由于对人的大脑结构作用不够了解,无法深刻的揭示作用原理,所以本作品采用自下而上的方法。 ## 手部识别原理 #### 优化器 优化器采用**Momentum SGD**优化器,初始学习率为0.0001,动量因子为0.9,权重衰减惩罚为0.0005 ```python optimizer = torch.optim.SGD(model.parameters(), lr=lr0, momentum=0.9, weight_decay=0.0005) ``` #### 学习率更新 每次遇到`milestones`中的epoch,做一次更新。 ```python scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[int(i) for i in lr_step.split(",")], gamma=0.1, last_epoch=start_epoch - 1) ``` ## 21关键点检测原理 采用ResNet预训练网络,进行21关键点检测。 #### 优化器 采用Adam优化器 ```python optimizer_Adam = torch.optim.Adam(model_.parameters(), lr=ops.init_lr, betas=(0.9, 0.99),weight_decay=1e-6) ``` 初始学习率为`1e-3`,梯度的运行平均值的系数为`(0.9, 0.99)`,权重衰减为`1e-6`。 #### 学习率更新 ```python # 变量初始化 best_loss = np.inf loss_mean = 0. # 损失均值 loss_idx = 0. # 损失计算计数器 flag_change_lr_cnt = 0 # 学习率更新计数器 init_lr = ops.init_lr # 学习率 if loss_mean!=0.: if best_loss > (loss_mean/loss_idx): flag_change_lr_cnt = 0 best_loss = (loss_mean/loss_idx) else: flag_change_lr_cnt += 1 if flag_change_lr_cnt > 50: init_lr = init_lr*ops.lr_decay set_learning_rate(optimizer, init_lr) flag_change_lr_cnt = 0 ``` ## 效果展示 1 ## 手势识别原理 有了手部识别和21关键点检测,手势识别就很容易实现了,手部关节标注如下: ![屏幕截图_2021-04-25_222145-removebg-preview](./image/2.png) 而手势识别的原理就是判断每个手指,如下图。 hand-output-Keypoints1 如果我想知道一个手部手指的状态,那么只需要知道每个手指的弯曲程度即可。比如我想判断食指的弯曲程度,可以通过度量手指的三个关节夹角,对应为5,6,7三个点出的夹角,可以通过向量直接算出。类似的就可求出每个手部关节的夹角,从而确定手部目前的状态。 判断手部夹角的函数在文件`detcet.py`中,通过`get_angle`函数即可获得所有关节夹角 ```python def get_angle(pts_hand): """ 获得当前手部的各个关节角度 :param pts_hand: 21关键点 :return: 手部各个关节角度 """ label_hand = np.empty([5, 3], dtype=int) for i in range(5): v1 = {} v2 = {} v1['x'] = pts_hand['0']['x'] - pts_hand[str(i * 4 + 1)]['x'] v1['y'] = pts_hand['0']['y'] - pts_hand[str(i * 4 + 1)]['y'] v2['x'] = pts_hand[str(i * 4 + 2)]['x'] - pts_hand[str(i * 4 + 1)]['x'] v2['y'] = pts_hand[str(i * 4 + 2)]['y'] - pts_hand[str(i * 4 + 1)]['y'] label_hand[i, 0] = angle(v1, v2) v3 = {} v4 = {} v3['x'] = pts_hand[str(i * 4 + 1)]['x'] - pts_hand[str(i * 4 + 2)]['x'] v3['y'] = pts_hand[str(i * 4 + 1)]['y'] - pts_hand[str(i * 4 + 2)]['y'] v4['x'] = pts_hand[str(i * 4 + 3)]['x'] - pts_hand[str(i * 4 + 2)]['x'] v4['y'] = pts_hand[str(i * 4 + 3)]['y'] - pts_hand[str(i * 4 + 2)]['y'] label_hand[i, 1] = angle(v3, v4) v5 = {} v6 = {} v5['x'] = pts_hand[str(i * 4 + 2)]['x'] - pts_hand[str(i * 4 + 3)]['x'] v5['y'] = pts_hand[str(i * 4 + 2)]['y'] - pts_hand[str(i * 4 + 3)]['y'] v6['x'] = pts_hand[str(i * 4 + 4)]['x'] - pts_hand[str(i * 4 + 3)]['x'] v6['y'] = pts_hand[str(i * 4 + 4)]['y'] - pts_hand[str(i * 4 + 3)]['y'] label_hand[i, 2] = angle(v3, v4) return label_hand ``` ## 应用 ### 物体识别 功能实现位于`handpose_garbage.py`中,本项目将物体识别对象设置为垃圾分类,即40种垃圾分类(可根据自己喜好更改不同的功能)。只需用两个手指选出一个方框,将想要检测的物体框入一个框中,系统会自动进行识别,并播报出识别结果。 在此之前,要确定选中状态。食指伸直并停留1秒左右认为是选中 屏幕截图 2021-04-25 230330 选中后,食指部位会出现一个红色的圈,提醒已经进入选中状态,并进行语音播报。 屏幕截图 2021-04-25 230247 而两个手同时进入选中状态下的话,就会以两个食指指尖的点所连成的线为对角线,画出一个矩形,截取图片,进行编码通过网络流传入后端运行的垃圾分类服务器中,再进行解码,进行垃圾识别,将识别结果传给手势识别服务器,并进行语音播报。 42520b0845c3fea95d41f6e98eab06d 当然,以上仅仅是基本原理,还有一些细节需要实现,如怎样防止计算机无限的进行识别,我们进行识别一段时间内一般仅需要一次,而计算机的运行速度是很快的,笔者采用GTX1650的显卡即可获得16FPS的帧率,而如果一秒内计算机进行16次的垃圾识别,显然是不需要的。 所以采用以下的逻辑(采用伪代码实现) ```python pose = [0, 0, 0, 0] # 标记点击手势的时长 flag = [0, 0, 0, 0] # 分别代表多个手中的每一个的食指是否伸开 # 两个手手指坐标 one_finger = [0, 0] other_finger = [0, 0] for index in indexs: if point: # 选中 pose[index]++ else: # 非选中 pose[index] = 0 # 持续15后进入选中模式,可以解决重复识别的问题。 if pose[index] > 15: drawCircle() if index == 0: one_finger_x = index_fingure_x # 食指的x坐标 one_finger_y = index_fingure_y # 食指的y坐标 if index == 1: other_finger_x = index_fingure_x # 食指的x坐标 other_finger_y = index_fingure_y # 食指的y坐标 elif pose[index] == 15: os.system('start pythonw ./utils/voice.pyw "选中"') ``` 如果有两个手指同时进入选中状态,则绘制矩阵。 ```python if flag[0] > 0 and flag[1] > 0: rectangel(image, one_finger_x, one_finger_y, other_finger_x, other_finger_y) # 绘制矩阵 if index == 1 and pose[1] == 30: # 如果有第二个手指头,并且已经进入选中30次 predict_img(one_finger_x, one_finger_y, other_finger_x, other_finger_y) # 预测矩阵中的内容 broadcast() # 进行语音播报 ``` ### 音乐控制 目前音乐控制可以通过手势实现`上一首`,`下一首`,`音量增加`,`音量减小`,`喜欢`,`暂停`,`继续播放`,七个功能。 音乐控制的逻辑与PPT控制的逻辑相同,但与物体识别的逻辑不同,音乐控制的逻辑较为复杂。 #### **get_gesture_control**函数 返回值`label_hand`,`label_hand[i][k](0 <= i <= 4, 0 <= k <= 2)`代表第`i`个手指的第`k`个关节处 #### 播放逻辑实现 **timer**:用来作为计时器,每过35帧归零,实现35帧判断一次手势 ```python if timer == 34 and control_timer == 0: ``` 如果过了35帧,并且还没有进入过判断手势状态,则开始进行手势判断。 **target**:手势概率分布靶 ```python target = [0, 0, 0, 0, 0] # 0: 无动作, 1: 播放/音乐, 2: 切换, 3: 喜欢, 4: 音量控制 ``` 在35帧内,每一帧检测到的结果计数器加一,到最后判断是那种手势的概率最大。 **target_gesture**:手势判断 ```python target_gesture = target.index(max(target)) ``` **control_timer**:已经进入控制状态的时间 **control_label**:是否进入控制状态 ```python target_gesture == 2 and control_timer == 15 ``` 如果为手势2,并且离进入控制状态过了15帧,就记录switch_new用来计算方向向量 **switch_old, switch_new**:用来通过old与new直间的向量判断切换方向,voice_old和voice_new同理 #### **gesture_action(gesture, label=0)**函数 通过gesture的值和label的值一起判断具体的手势。 #### 暂停/播放 08276cdecf39f17d1e30fd16aac23de #### 控制音量 c2257783b539d63d57a930a6a11a41e ### PPT翻页控制 可以通过手势实现PPT的上下翻页 bbca1ddb6b766c3cdf0a3f8ca967eb3 ### TODO - 控制智能家居,如窗帘,电视,灯的开关 - 视频播放(快进,快退,暂停,播放)