上一篇文章介绍了基于opencv的手势识别,如果大家运行了我的代码,会发现代码中找出手部轮廓的效果不是很理想。当时我在网上找寻解决的办法,刚好找到了mediapip库,然后我就利用opencv和mediapipe这两个库重新进行了手势识别的代码编写。效果还不错,写篇文章记录一下。
1.mediapipe简介
Mediapipe是google的一个开源项目,可以提供开源的、跨平台的常用机器学习(machine learning)方案。Mediapipe实际上是一个集成的机器学习视觉算法的工具库,包含了人脸检测、人脸关键点、手势识别、头像分割和姿态识别等各种模型。
由于我主要做的是手势识别,我就主要简单地讲解一下该库的手部检测模块,以便大家能更好的理解最后的手势识别的源代码。(如果大家想要了解其他模块,可以点击这里的进行了解:mediapipe官方简介
首先进行手部检测模块的初始化
mphand = mp.solutions.hands
hands = mphand.Hands()
mpHand.Hands参数参数详解static_image_mode=False如果设置为False ,减少了延迟,非常适合处理视频帧。如果设置为True ,适合处理一批可能不相关的静态图像。默认为False 。max_num_hands=2要检测的最大手数。默认为2model_complexity=1手部标志模型的复杂性:0或1 .地标精度和推理延迟通常随着模型复杂性的增加而增加。默认为 1min_detection_confidence=0.5手部检测模型中的最小置信度值 ,以便将检测视为成功。默认为 0.5min_tracking_confidence=0.5地标跟踪模型中的最小置信度值 ,表示手部地标被视为成功跟踪,否则将在下一个输入图像上自动调用手部检测。将其设置为更高的值可以提高解决方案的健壮性,但代价是延迟更高。如果static_image_mode为 True,则忽略,其中手部检测只是在每个图像上运行。默认为0.5
如果不太理解上面的参数含义,没关系,直接默认就好。我认为只要知道max_num_hands和min_detection_confidence两个参数的含义就可以了
接着就是手势检测过程
hand = hands.process(img)
参数img是要进行检测的图片,由于opencv读入的图片为BGR模式,这里的img一定要转换为RGB模式
最后就是读取检测后的结果
finger = []
for handlms in hand.multi_hand_landmarks:
for lm in handlms.landmark:
img_height,img_width,_ = img.shape
#这里检测返回的x,y的坐标是基于原图像大小的比例坐标,
#所以要与原图像相乘得到真实的坐标
x, y = int(lm.x * img_width), int(lm.y * img_height)
finger.append([x, y])
如果打印一下finger列表,你就会看到21个坐标值。这21个坐标就是midiapipe基于人体手部21个关键点在原图像上位置坐标的检测。这21个坐标值对应的手部检测的关键点如下图:
2.手势识别思路
mediapipe的基本用法大家已经了解了,现在讲解思路以及关键技巧的讲解
思路讲解
首先使用opencv调用摄像头,从摄像头读取图像,将图像反转(摄像头读取的图像与现实中是相反的),并将图像转换为RGB模式。接着使用mediapipe进行手部检测,并用列表存储。然后利用手指构成的角度来判断手指是否弯曲。
关键点讲解
整个程序似乎是没有什么难度的,主要的关键就是如何检测手指是否弯曲。
引入角度来解决问题
引入三角函数,用手指构成的角度大小来进行判断
举个例子,这里我采用5,6,7坐标点为顶点来构成的三角形,这里我
以5,6坐标构成的边为a,
以6,7坐标构成的边为b,
以5,7坐标构成的边为c,
然后利用余弦函数来计算顶点6的角度,公式为:
a
2
+
b
2
−
c
2
/
2
a
b
a^2+b^2-c^2/2ab
a2+b2−c2/2ab
然后设置一个阈值为155,来判断手指是否弯曲。
3.完整代码
这里我对代码进行了注释,帮助大家阅读程序
import cv2
import numpy
import mediapipe as mp
import math
#创建手部检测的对象
mphand = mp.solutions.hands
hands = mphand.Hands()
mpdraw = mp.solutions.drawing_utils
cap = cv2.VideoCapture(0)while cap.isOpened():
#对读取的图像进行反转,转换为RGB模式,读出图片大小
ret,frame = cap.read()
frame = cv2.flip(frame,1)
img =cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
h,w,_ = frame.shape
#进行手部的检测
hand = hands.process(img)
#判断是否检测到了手部
if hand.multi_hand_landmarks:
count =0 #统计手指的伸出个数
for handlms in hand.multi_hand_landmarks:
finger =[]
finger_point =[]
#这里我将每一根手指的四个坐标整合到一个列表中,大家可以打印finger,进行详细的查看
for id,lm inenumerate(handlms.landmark):
x,y =int(lm.x*w),int(lm.y*h)if id ==0:
pass
elif id %4==0:
finger_point.append([x,y])
finger.append(finger_point)
finger_point =[]else:
finger_point.append([x,y])
#遍历每一根手指列表,计算其构成的三角形的三边长,这里使用2,6,10,14,18所对应的角进行判断
for id,point inenumerate(finger):
a = math.hypot((point[0][0]-point[1][0]),(point[0][1]-point[1][1]))
b = math.hypot((point[1][0]-point[2][0]),(point[1][1]-point[2][1]))
c = math.hypot((point[0][0]-point[2][0]),(point[0][1]-point[2][1]))
#在计算value值的时候,除数可能为零,以及当三点在一点直线上,都会抛出异常,所以要捕获异常
try:
value =(a**2+b**2-c**2)/(2*a*b)
#这里的value为弧度制,乘上57转换为角度制,当然你也可以选择直接使用弧度制
angle = math.acos(value)*57
except ValueError:
angle =180
except ZeroDivisionError:
angle =0print(angle)
#当角度大于155的时候记为手指伸出
if angle >=155:
count +=1else:
pass
#在手部绘制关键点位置
mpdraw.draw_landmarks(frame,handlms,mphand.HAND_CONNECTIONS)
#将手指检测的结果显示在图像上
cv2.putText(frame,str(count),(int((1/9)*w),int((1/9)*h)),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),1)
#展示图片
cv2.imshow('img',frame)
#按下Esc退出循环
c = cv2.waitKey(25)if c ==27:break
cap.release()
cv2.destroyAllWindows()
4.结尾
使用mediapipe库来检测手部,这让我们可以通过手势来完成很多有意思的事情,比如通过手势来控制电脑鼠标,手势作画等。这些我也在尝试去做,等我完成之后会分享给大家思路,以及遇到的困难。
最后创作不易,希望大家支持,点赞。期待大家能和我一块交流学习,共同进步。
版权归原作者 困了不能睡 所有, 如有侵权,请联系我们删除。