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)
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:
lst = [0, 0, 0]
distanceDict = {}
success, img = cap.read()
imgRGB= cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
results = hands.process(imgRGB)
if results.multi_hand_landmarks:
for handLms in results.multi_hand_landmarks:
for id, lm in enumerate(handLms.landmark):
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)
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
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
key = cv2.waitKey(1)
if key == 27:
cv2.destroyAllWindows()
break
?
????????
~~~~~~~~
????????建立新的项目文件,拷贝上述代码运行(前提Python 已经成功配置OpenCV 的环境)
-
如果你的程序成功运行(摄像头窗口正常调用,python 程序输出正常)且没有出现任何报错,那么恭喜你,你的环境配置成功,接下来的问题解决环节你已经可以跳过。 -
如果你的程序报错,无法执行,那么同样恭喜你,接下来会将拯救你于苦海:
程序的报错可能分为三种情况:
-
FileNotFoundError: The path does not exist.** 报错,出现此类错误是由于python 解释器的安装路径中存在中文字符导致。
小Tips:
注意此处为解释器的安装路径,而非项目文件路径
-
[ 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版本不兼容导致的问题,由于不影响程序的正常运行,可以不同管 -
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,我们已经发现问题,下面开始介绍上述问题解决办法:
????????
~~~~~~~~
???????? 这个警告其实可以不用管,只要不影响程序的运行即可,但是也可以将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):
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)
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
上述代码展示如何计算四个手指的指尖到掌根的距离:
- id代表手掌21个不同的关键结点
- lm存储每个结点的xyz坐标,可以通过lm.x lm.y lm.z的方式进行获取
- id=4,8,12,20分别代表手掌的大拇指,中指,食指,小拇指,id=0代表掌根
- 将四个手指到指尖的距离存入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确定命令:
- 在字典不为零的情况下,取出字典中距离最远的手指id
- 根据实现建立的映射关系,转化为相应的控制命令
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帧图像的目的在于防止手指切换过程中可能造成的误差)
- commandLst用于存储10帧图像的命令
- 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 安装模块时。
- 找到python37和39的安装路径
- 将python文件夹下python.exe分别改为python37.exe和python39.exe
?
????????
~~~~~~~~
????????OK,经过上述操作,当你想通过终端利用pip安装模块时,就可以借助于如下命令:python37 -m pip install numpy或者python39 -m pip install numpy,其他类似的命令也可以采取上述方式。
|