起因:2023年4月接触树莓派之后,想实现手指方向的实时识别,现有Google的框架Mediapipe可以实现21个手指关键点位置实时识别,去尝试装Mediapipe以及相关依赖库,发现树莓派的源(官方源),一些库比较旧(15年的都有),所以整体思想要装15年 16年的版本,才能适配
系统环境搭建:
1.系统选择:官方镜像下载Legacy版本Operating system images – Raspberry Pi
相关系统配置如下图:Python3.7.3,Pip18.1,Numpy1.16.2都可以用Pip更新一下
2.没有显示屏的话,需要先预埋ssh启动和Wifi自动连接,这里需要笔记本和树莓派连同一个网,烧录结束后,拔下SD再插到电脑上,把 wpa_supplicant.conf 和 ssh 扔到 root 里面:(ssh是新建文档.txt直接改的,wpa_supplicant.conf 用记事本打开输入下面)
country=CN
ctrl_interface=DIR=/var/run/wpa_supplicant
update_config=1
network={
ssid="和电脑在同一个wifi下的热点名"
psk="密码"
key_mgmt=WPA-PSK
}
3.通过和PC在同一个网络下,用Advanced IP Scanner软件找到IP地址,用Putty连接ssh,第一次开启VNC开机,设置VNC自启给树莓派配置VNC,设置开机自启_树莓派vnc自启动_9677的博客-CSDN博客
4.关于换源的问题,如果官方源不卡,包括后面可以选择手动下载安装,官方源挺好,如果卡了换树莓派换源教程_萌新源的博客-CSDN博客
5.装Opencv和拓展库以及Mediapipe,我是跟于泓老师的教程树莓派-Opencv+mediapipe安装全过程_哔哩哔哩_bilibili
(相关文件视频下面也有)百度云链接:https://pan.baidu.com/s/1jUIh4wJgKB7I6fDX5fkGwQ
提取码:sm00
6.我跟着视频装Mediapipe-rpi4 之后import会报错,搜到mediapipe 安装笔记-1:使用anaconda python 3.7.13__message.message._checkcalledfromgeneratedfile()_不解不惑的博客-CSDN博客
protobuf版本高了,需要卸载protobuf 装protobuf==3.20版本,
pip uninstall protobuf
pip install protobuf==3.20
之后import mediapipe就没问题了(需要装的软件都在百度云里)之后再手动pip装一个
1)cvzone是一个计算机视觉工具包,可方便的图像处理和实现视觉AI功能。核心是使用OpenCV和Mediapipe库。安装使用都十分简单方便 pip3 install cvzone
2)Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式,
代码运行(10月21日更新)
手指方向检测(食指, 可以修改其他手指) 可以直接运行,这里的hands_module是单独检测左手或者右手,test_time检测时间就是超时返回0,last_dire是我项目里需要的参数,这应该是最快速的检测手指指向的方案了,包含了手指的方向以及手指是否直指,(食指一二三关节关键点夹角)这里是手指关节必须大于140度
效果图:
手指指向判断时候,cvzone手势识别有,手掌21个关键点的三维坐标,所以这里利用的是三维向量判断和对应角度夹角,这里三维向量∠ABC需要满足需求才表示指向某个方向。
检测最大的那只手,指向判断,完成功能。
# -*- coding: utf-8 -*-
import cv2
import time
import numpy as np
import cvzone.HandTrackingModule
import cvzone.FaceDetectionModule
from collections import Counter
face_detector = cvzone.FaceDetectionModule.FaceDetector(minDetectionCon=0.5)
hand_direction_detector = cvzone.HandTrackingModule.HandDetector(mode=False, # 视频流图像
maxHands=4, # 最多检测两只手
detectionCon=0.5, # 最小检测置信度
minTrackCon=0.7) # 最小跟踪置信度
def detect_hand_direction(cap_hand_direction, hands_module=1, last_dire=5, test_time=5):
print("开始指向检测")
reference_vectors = [np.array([0, -1, 0]), np.array([1, 0, 0]),
np.array([-1, 0, 0]), np.array([0, 1, 0])] # 3D坐标单位向量
angles = []
add_count = [1, 1, 1, 1]
left_hand_direction = [0, 0, 0, 0]
right_hand_direction = [0, 0, 0, 0]
video_model = 1 # 是否显示视频
if 0 < last_dire < 5: # 平常是5 不作数, 如果对应了某一个方向,那就让对应方向检测次数更多
add_count[last_dire-1] = 1/3
# 清除之前的视频流数据
frames_to_skip = 2 # 要丢弃的帧数
for _ in range(frames_to_skip):
_, _ = cap_hand_direction.read()
atime = time.time()
while True:
success, frame = cap_hand_direction.read()
if success is not True:
break
hands = hand_direction_detector.findHands(frame, draw=False, flipType=False)
if hands:
left_hand_max_area = 0 # 初始化最大面积为0
right_hand_max_area = 0 # 初始化最大面积为0
left_hand_max_index = -1 # 初始化最大面积对应的遍历序号为-1
right_hand_max_index = -1 # 初始化最大面积对应的遍历序号为-1
left_region_status = 0 # 初始化左手是否在范围内
right_region_status = 0 # 初始化右手是否在范围内
for index, hand in enumerate(hands):
if hand["type"] == "Left": # 左手
_, _, w_left, h_left = hand["bbox"]
left_hand_area = w_left * h_left # 计算面积
if left_hand_area > left_hand_max_area:
left_hand_max_area = left_hand_area
left_hand_max_index = index
if hand["type"] == "Right": # 右手
_, _, w_right, h_right = hand["bbox"]
right_hand_area = w_right * h_right # 计算面积
if right_hand_area > right_hand_max_index:
right_hand_max_index = right_hand_area
right_hand_max_index = index
if hands_module == 2 and left_hand_max_index != -1:
# 模式2 只检测左手
# # 采用食指的第一关节[7]第二关节[6]第三关节即指根[5]来求出 食指第二关节的角度 计算食指弯曲角度
# # 将夹角余弦值转换为角度制并输出夹角
ab = np.array(hands[left_hand_max_index]["lmList"][6]) - np.array(
hands[left_hand_max_index]["lmList"][7])
bc = np.array(hands[left_hand_max_index]["lmList"][5]) - np.array(
hands[left_hand_max_index]["lmList"][6])
cos_angle = np.dot(ab, bc) / (np.linalg.norm(ab) * np.linalg.norm(bc))
angle = 180 - np.arccos(cos_angle) * 180 / np.pi
if angle > 140:
direction = np.array(hands[left_hand_max_index]["lmList"][5]) - np.array(
hands[left_hand_max_index]["lmList"][8])
angles.clear()
for reference_vector in reference_vectors:
cos_angle = np.dot(direction, reference_vector) / (
np.linalg.norm(direction) * np.linalg.norm(reference_vector))
angle = np.arccos(cos_angle) * 180 / np.pi
angles.append(angle)
# 判断手指朝向是否符合四个方向中的至少一个
if angles[0] < 45 <= max(angles): # 下
left_hand_direction.append(1)
elif angles[1] < 45 <= max(angles): # 左
left_hand_direction.append(2)
elif angles[2] < 45 <= max(angles): # 右
left_hand_direction.append(3)
elif angles[3] < 45 <= max(angles): # 上
left_hand_direction.append(4)
if len(left_hand_direction) >= 10:
counter = Counter(left_hand_direction) # 使用Counter计算每个数字的出现次数
most_common_digit = counter.most_common(1)[0][0] # 找到出现次数最多的数字
print("Most common digit:", most_common_digit)
return most_common_digit
if hands_module == 1 and right_hand_max_index != -1:
# 模式1 只检测右手
# 采用食指的第一关节[7]第二关节[6]第三关节即指根[5]来求出 食指第二关节的角度 计算食指弯曲角度
# # 将夹角余弦值转换为角度制并输出夹角
ab = np.array(hands[right_hand_max_index]["lmList"][6]) - np.array(
hands[right_hand_max_index]["lmList"][7])
bc = np.array(hands[right_hand_max_index]["lmList"][5]) - np.array(
hands[right_hand_max_index]["lmList"][6])
cos_angle = np.dot(ab, bc) / (np.linalg.norm(ab) * np.linalg.norm(bc))
angle = 180 - np.arccos(cos_angle) * 180 / np.pi
if angle > 140:
direction = np.array(hands[right_hand_max_index]["lmList"][5]) - np.array(
hands[right_hand_max_index]["lmList"][8])
angles.clear()
for reference_vector in reference_vectors:
cos_angle = np.dot(direction, reference_vector) / (
np.linalg.norm(direction) * np.linalg.norm(reference_vector))
angle = np.arccos(cos_angle) * 180 / np.pi
angles.append(angle)
# 判断手指朝向是否符合四个方向中的至少一个
if angles[0] < 45 <= max(angles): # 下
right_hand_direction.append(1)
elif angles[1] < 45 <= max(angles): # 左
right_hand_direction.append(2)
elif angles[2] < 45 <= max(angles): # 右
right_hand_direction.append(3)
elif angles[3] < 45 <= max(angles): # 上
right_hand_direction.append(4)
if len(right_hand_direction) >= 10:
counter = Counter(right_hand_direction) # 使用Counter计算每个数字的出现次数
most_common_digit = counter.most_common(1)[0][0] # 找到出现次数最多的数字
print("Most common digit:", most_common_digit)
return most_common_digit
if time.time() - atime > test_time:
cv2.destroyAllWindows()
return 0
if video_model == 1:
cv2.imshow('hand_direction', frame)
if cv2.waitKey(1) & 0xFF == 27: # 每帧滞留20毫秒后消失,ESC键退出
break
if __name__ == '__main__':
Cap = cv2.VideoCapture(0)
# 检测 显示
b = detect_hand_direction(Cap, last_dire=2, test_time=10)
print(b)
总结:
运行下来,cvzone模型本身是谷歌的mediapipe中应用,检测效果非常好,利用起来占用资源也很少,已经应用在了树莓派等linux平台,端上运行速度可以接受,但是由于我项目本身的需求,对于各种手指指向判断时候,容易受到遮挡的影响,比如某个手势中手掌会遮挡大部分手指,就不会识别到手掌。当然cvzone中的手掌识别主要是为了追踪手掌,所以在随后的研究中将使用特定的手势训练,而不是获取手指关键点坐标来推算手指手势。