# 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
```
## 效果展示
## 手势识别原理
有了手部识别和21关键点检测,手势识别就很容易实现了,手部关节标注如下:

而手势识别的原理就是判断每个手指,如下图。
如果我想知道一个手部手指的状态,那么只需要知道每个手指的弯曲程度即可。比如我想判断食指的弯曲程度,可以通过度量手指的三个关节夹角,对应为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秒左右认为是选中
选中后,食指部位会出现一个红色的圈,提醒已经进入选中状态,并进行语音播报。
而两个手同时进入选中状态下的话,就会以两个食指指尖的点所连成的线为对角线,画出一个矩形,截取图片,进行编码通过网络流传入后端运行的垃圾分类服务器中,再进行解码,进行垃圾识别,将识别结果传给手势识别服务器,并进行语音播报。
当然,以上仅仅是基本原理,还有一些细节需要实现,如怎样防止计算机无限的进行识别,我们进行识别一段时间内一般仅需要一次,而计算机的运行速度是很快的,笔者采用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的值一起判断具体的手势。
#### 暂停/播放
#### 控制音量
### PPT翻页控制
可以通过手势实现PPT的上下翻页
### TODO
- 控制智能家居,如窗帘,电视,灯的开关
- 视频播放(快进,快退,暂停,播放)