admin 管理员组

文章数量: 887021

起因: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/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中的手掌识别主要是为了追踪手掌,所以在随后的研究中将使用特定的手势训练,而不是获取手指关键点坐标来推算手指手势。

本文标签: 手指 方向 树莓派 Mediapipe opencv