简单记录一次MediaPipe手势识别过程(附详细代码及问题解决办法)


Author:qyan.li

Date:2022.6.16

Topic:简单记录一次MediaPipe手势识别的过程(附算法思想和问题解决办法)

Reference:https://zhuanlan.zhihu.com/p/391844369

一、写在前面

         ~~~~~~~~         前段时间借助于树莓派(Raspberry)构建手势控制系统,需要实现手势识别功能。由于之前使用MediaPipe实现过姿态识别的功能,效果不错。因此,此次决定还是借助于MediaPipe实现手势识别,调用实现的过程问题不断,写篇博文记录一下心路历程,供大家学习参考。
         ~~~~~~~~         树莓派智能小车的博文也已经更新,参考连接:https://blog.csdn.net/DALEONE/article/details/125241860?spm=1001.2014.3001.5501

二、准备工作及问题解决

         ~~~~~~~~         首先,对于MediaPipe手势识别功能进行简要说明,一句话概括:MediaPipe手势识别可以检测出手部的21个关键结点,速度快,效果佳。

         ~~~~~~~~         MediaPipe的安装类似于其他python模块的安装,没有什么特殊的地方,借助于命令:pip install MediaPipe

         ~~~~~~~~         安装完成之后,可以借助于如下代码验证安装下来的MediaPipe能否正常使用,测试的代码放置在下面(这其实也是我们后面手势识别的代码)

import cv2
import mediapipe as mp
import time
import traceback
import socket
import sys


commandDict = {'right':'10','left':'01','forward':'11','backforward':'00','unKnown':'-1'}

cap = cv2.VideoCapture(0)
# cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)
mpHands = mp.solutions.hands
hands = mpHands.Hands(static_image_mode=False,
                      max_num_hands=2,
                      min_detection_confidence=0.5,
                      min_tracking_confidence=0.5)
mpDraw = mp.solutions.drawing_utils

pTime = 0
cTime = 0

# 全局变量
frameNum = 0
commandLst = []
commandSending = ''

flag = 0


while True:
    # 初始化0关键点的坐标
    lst = [0, 0, 0]
    # 初始化字典
    distanceDict = {}

    success, img = cap.read()
    imgRGB= cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    results = hands.process(imgRGB)
    # print(results.multi_hand_landmarks)


    if results.multi_hand_landmarks:
        for handLms in results.multi_hand_landmarks:
            for id, lm in enumerate(handLms.landmark):
                ## 存储0关键点的三个坐标
                if id == 0:
                    lst = [lm.x,lm.y,lm.z]
                h, w, c = img.shape
                cx, cy = int(lm.x *w), int(lm.y*h)
                ## 绘制手部关键结点
                cv2.circle(img, (cx,cy), 3, (255,0,255), cv2.FILLED)

                ## 分别检测4,8,12,20四个关键结点与0结点间的距离判断手指指向
                if id == 4 or id == 8 or id == 12 or id == 20:
                    distanceSum = 0
                    distanceSum = (lst[0]-lm.x)**2 + (lst[1]-lm.y)**2 + (lst[2]-lm.z)**2
                    distanceDict[id] = distanceSum

            ## mediapipe中首部关键结点的连线
            mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)

    command = 'UnKnown'
    if distanceDict == {}:
        MaxId = 0
    else:
        MaxId = [key for key,value in distanceDict.items() if value == max(distanceDict.values())][0]
    ## 判断哪个结点距离根节点最远,并由此给出相应的命令
    if MaxId == 4:
        command = 'right'
    if MaxId == 8:
        command = 'left'
    if MaxId == 12:
        command = 'forward'
    if MaxId == 20:
        command = 'backforward'
    if MaxId == 0:
        command = 'unKnown'
    cv2.putText(img,command,(10,150), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3)

    ## 计算并显示帧率
    cTime = time.time()
    fps = 1/(cTime-pTime)
    pTime = cTime

    cv2.putText(img,str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3)

    cv2.imshow("Image", img)

    ## 连续判断十帧图像
    frameNum += 1
    commandLst.append(command)
    if frameNum == 5:
        if max(commandLst) == min(commandLst):
            commandSending = commandDict[command]
        else:
            commandSending = commandDict['unKnown']

        ## 数据重新置零
        frameNum = 0
        commandLst = []

        print(commandSending)
    else:
        continue

    ## 此处设置退出键esc,按下esc退出窗口
    key = cv2.waitKey(1)
    if key == 27:
        #通过esc键退出摄像
        cv2.destroyAllWindows()
        break

         ~~~~~~~~         建立新的项目文件,拷贝上述代码运行(前提Python已经成功配置OpenCV的环境)

  • 如果你的程序成功运行(摄像头窗口正常调用,python程序输出正常)且没有出现任何报错,那么恭喜你,你的环境配置成功,接下来的问题解决环节你已经可以跳过。

  • 如果你的程序报错,无法执行,那么同样恭喜你,接下来会将拯救你于苦海:

    程序的报错可能分为三种情况:

    1. FileNotFoundError: The path does not exist.**报错,出现此类错误是由于python解释器的安装路径中存在中文字符导致。

      Tips:

      注意此处为解释器的安装路径,而非项目文件路径

    2. [ WARN:0@3.614] global D:\a\opencv-python\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (539) `anonymous-namespace'::SourceReaderCB::~SourceReaderCB terminating async callback警告,由于Opencv版本不兼容导致的问题,由于不影响程序的正常运行,可以不同管

    3. cv2.error: OpenCV(4.5.5) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src\color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cv::cvtColor'此种错误网络上说法不一,但是自己是由于电脑本身的摄像头连接导致的错误


             ~~~~~~~~         OK,我们已经发现问题,下面开始介绍上述问题解决办法:

    • python解释器中文路径问题

               ~~~~~~~~         不清楚大家什么状况,自己解释器含有中文路径是由于系统名称为中文,导致python安装的位置中含有中文,可能的解决办法:

      1. 修改系统的名称为中文—一劳永逸,但是可能存在系统崩溃,文件丢失的风险,权衡过后被我pass掉
      2. 修改python解释器的安装位置—个人感觉结尾复杂,同时涉及环境变量以及诸多问题,最终也被pass

               ~~~~~~~~         那自己是采取的什么措施,安装一个新的python解释器,放置在全英文字符路径下,重新配置下环境,MediaPipe项目使用新的解释器即可,现在python已经出到3.10。

    • 警告问题:

             ~~~~~~~~          这个警告其实可以不用管,只要不影响程序的运行即可,但是也可以将cap = cv2.VideoCapture(0)改为cap = cv2.VideoCapture(0,cv2.CAP_DSHOW),会发现此时项目运行的警告已经消失

    • cv2.error报错问题

               ~~~~~~~~         这个问题网络上的解释比较多,比如路径,通道数等等,我都尝试过,但都不太符合自己运行环境的实际情况。折磨很久,检查很多遍,最终发现是摄像头连接的问题,那怎么检查你的报错情况是否是摄像头的问题呢?很简单,手动打开电脑的摄像头,看看能否正常工作,如果不能,那大概率就是我的这种情况。

               ~~~~~~~~         那如果真的是,如何解决呢?那你就可以检索windows下摄像头无法正常工作的文章,提供几个思路,应该是可以解决的:

      • 重启大法,重启下电脑试试,没准真的可以解决

      • 调整下屏幕倾角,按一下键盘与屏幕连接处(考虑接触不良问题,尤其是动过电脑的同学)

        自己就是这种情况,由于之前换过电脑屏幕,可能接触不是特别好

      • 更新下摄像头的驱动

      • 其他方法参考其他博文即可


         ~~~~~~~~         OK,问题解决完,代码可以正常运行,我们来看一下代码是怎样实现手势识别的,思想非常简单,用一句话概括:计算指尖到手掌根部的距离实现手势的识别。手势识别算法设计是需要和你的需求相匹配,自己是利用手势控制小车的前进,停止,左转,右转。命令简单,且仅具有4中分类,所以,上述的算法可以很好的进行适配。但是假设你的需求较为复杂,就需要更高阶的算法实现手势识别。

         ~~~~~~~~         先放一张MediaPipe手部21关键节点的图片,方便大家后续参考:
在这里插入图片描述

 for id, lm in enumerate(handLms.landmark):
        ## 存储0关键点的三个坐标
        if id == 0:
            lst = [lm.x,lm.y,lm.z]
            h, w, c = img.shape
            cx, cy = int(lm.x *w), int(lm.y*h)
            ## 绘制手部关键结点
            cv2.circle(img, (cx,cy), 3, (255,0,255), cv2.FILLED)

            ## 分别检测4,8,12,20四个关键结点与0结点间的距离判断手指指向
            if id == 4 or id == 8 or id == 12 or id == 20:
                distanceSum = 0
                distanceSum = (lst[0]-lm.x)**2 + (lst[1]-lm.y)**2 + (lst[2]-lm.z)**2
                distanceDict[id] = distanceSum

上述代码展示如何计算四个手指的指尖到掌根的距离:

  1. id代表手掌21个不同的关键结点
  2. lm存储每个结点的xyz坐标,可以通过lm.x lm.y lm.z的方式进行获取
  3. id=4,8,12,20分别代表手掌的大拇指,中指,食指,小拇指,id=0代表掌根
  4. 将四个手指到指尖的距离存入distanceDict字典,方便后续调用
command = 'UnKnown'
    if distanceDict == {}:
        MaxId = 0
    else:
        MaxId = [key for key,value in distanceDict.items() if value == max(distanceDict.values())][0]
    ## 判断哪个结点距离根节点最远,并由此给出相应的命令
    if MaxId == 4:
        command = 'right'
    if MaxId == 8:
        command = 'left'
    if MaxId == 12:
        command = 'forward'
    if MaxId == 20:
        command = 'backforward'
    if MaxId == 0:
        command = 'unKnown'
    cv2.putText(img,command,(10,150), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3)

这段代码展示借助于distanceDict确定命令:

  1. 在字典不为零的情况下,取出字典中距离最远的手指id
  2. 根据实现建立的映射关系,转化为相应的控制命令
    ## 连续判断十帧图像
    frameNum += 1
    commandLst.append(command)
    if frameNum == 5:
        if max(commandLst) == min(commandLst):
            commandSending = commandDict[command]
        else:
            commandSending = commandDict['unKnown']

        ## 数据重新置零
        frameNum = 0
        commandLst = []

        print(commandSending)
    else:
        continue

这段代码表示连续判断十帧图像,生成最终的命令:

(连续判断10帧图像的目的在于防止手指切换过程中可能造成的误差)

  1. commandLst用于存储10帧图像的命令
  2. max(commandLst)==min(commandLst)判断10帧图像命令是否相同,相同命令即确定,不同即为unKnown

三、写在最后

         ~~~~~~~~         写到这里,今天的主要内容已经完成,最后写一点题外话,是由上面python解释器安装引申来的。假设有的同学真的采用上述方法下载新的解释器,那么自己的本机上就应该不止有一个解释器,那么如何管理他们呢?这其实在自己刚玩python时是困扰自己挺长时间的一个问题。

         ~~~~~~~~         拿我自己的电脑来说,python解释器有三个版本:3.7,3.9,3.10,且按照教程添加环境变量,此时在终端中输入python显示的为3.10即你最近下载的版本,使用pip install安装模块时默认也肯定会安装在3.10的解释器下,那么如何访问3.7和3.9的解释器呢?尤其时在pip install 安装模块时。

  1. 找到python37和39的安装路径
  2. 将python文件夹下python.exe分别改为python37.exe和python39.exe

         ~~~~~~~~         OK,经过上述操作,当你想通过终端利用pip安装模块时,就可以借助于如下命令:python37 -m pip install numpy或者python39 -m pip install numpy,其他类似的命令也可以采取上述方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值