mediapipe包实现简单的手部识别
前言
一开始我还是java和c++的忠实用户,对python多少有点轻视,但很快就发现我错了,python没有java和c++那样严格的各种语法和格式要求,可以说是非常简易的。而且python有着大量的第三方库,可以轻轻松松用十几行代码实现各种各样的功能,代码写起来还是比c++和java轻松许多的。最近也在学习python里的一些第三方库,就写篇文章记录一下学习成果和笔记吧。
环境配置
我的开发环境是python3.8+pycharm,这次主要用到的包就是mediapipe和opcv-python,怎么下载这两个包我就不说了,如果有不会的可以自行百度。下面就先简单介绍一下这两个包吧,(只是简单介绍一下这些包在这个案例里要干什么以及可以干什么,具体的我也不是太了解了)。
MediaPipe
MediaPipe 是一款由 Google Research 开发并开源的多媒体机器学习模型应用框。我们可以直接获取到谷歌已经训练好的识别模型,通过接口调用对图片或视频里的人进行手部、脸部或者身体骨架的识别,这些模型我们也可以使用自己的数据进行训练,这里不再多说。在这个案例里面我们要使用手部检测的模型。
首先我们先看一下GitHub上这部分的文档上的示例代码。
#导入opencv-python的cv2模块
import cv2
#导入mediapipe模块
import mediapipe as mp
#获取绘制特征点的工具类
mp_drawing = mp.solutions.drawing_utils
#这个应该是设置绘制时的样式的
mp_drawing_styles = mp.solutions.drawing_styles
#获取手部识别模型的类
mp_hands = mp.solutions.hands
#创建一个识别模型类的对象供我们使用
hands=mp_hands.Hands()#创建的时候可以设置许多参数,这里我就采用默认值
mp.solutions
里面有各种我们可能用到的类,比如脸部识别的,手部识别,绘制特征点的等等,需要什么就去这里面拿就行了。
接着就是用我们的模型去识别图片或者视频。
#使用该对象身上的process方法传进来一个RGB格式的图片,会把识别结果进行返回
results = hands.process(img)
#results身上的multi_hand_landmarks会存放是否检测到该图形中有手,如果检测到手的检测信息存放在里面
if results.multi_hand_landmarks:
#遍历每只手
for hmLandmarks in results.multi_hand_landmarks:
#遍历每只手上的特征点
for idx,f in enumerate(hmLandmarks.landmark):
print(idx,f)
每只手上都有21个监测点,每个点都有对应的坐标信息,但这些坐标是已经进行归一化处理的,我们需要乘上窗口的大小获得实际的坐标值。下面是在网上找到的手部标志点的图片,开发时可以参考这个。
opencv-python
opencv搞过计算机图形学或者人工智能方面的应该都不陌生,经常用来进行图像处理,我了解也不是特别深入,就主要说一下这个案例里面我们怎么用到的这个包。如果我们想实时的用我们摄像头对我们的手进行识别,需要通过这个包开启摄像头拍照,然后再将每一帧的图片用mediapipe的模型进行识别再把识别结果输出即可。
import cv2
#获取摄像头,0为默认计算机默认摄像头,1可以更换来源
cap = cv2.VideoCapture(0)
while True:
#不断的获取当前的图片,返回获取的状态和获取到的图片(图片是BGR格式的)
sat,img=cap.read();
#将图片显示到窗口
cv2.imshow("Image",img)
#等待一毫秒,这样才能看到拍摄到的图片
cv2.waitKey(1)
正式开始
准备工作都做完之后,我们就可以正式开始工作了,下面先把全部的代码和运行结果展示出来。
import cv2
import mediapipe as mp
import time
cap=cv2.VideoCapture(0)
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils
#计算帧率
pTime=0
cTime=0
while True:
success,img=cap.read()
#因为我们获取到的图像是GBR的,所以要转换成RGB格式再交给模型处理
imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#result里面存放的是我们每次的检测结果
result=hands.process(imgRGB)
#print(result.multi_hand_landmarks)
#如果画面检测到了手就绘制手上的点
if result.multi_hand_landmarks:
for handLms in result.multi_hand_landmarks:#可能检测到了多个手,遍历这些手
for id,lm in enumerate(handLms.landmark):#每个手上都有21个点的信息,遍历每个手上的所有点的信息
h,w,c=img.shape#获取窗口的高和宽
cx,cy=int(lm.x*w),int(lm.y*h)#算出这个点的中心位置
#在我们的手腕出画一个圆
if id==0:
cv2.circle(img,(cx,cy),15,(40,123,222),cv2.FILLED)#在这个点的中心位置画圆
#使用绘制工具将所有的点连接起来
mpDraw.draw_landmarks(img,handLms,mpHands.HAND_CONNECTIONS)#绘制手上的点并将点用线连起来
#计算帧率
cTime=time.time()
fps=1/(cTime-pTime)
pTime=cTime
#将帧率输出到窗口上
cv2.putText(img,str(int(fps)),(10,70),cv2.FONT_HERSHEY_PLAIN,3,(40,123,222),3)
cv2.imshow("Image",img)
cv2.waitKey(1)
这样我们就可以得到手部的所有特征点的位置信息了,有了这些信息,我们就能够做各种各样的事情了,比如手势识别、通过手指的移动操作鼠标、手势控制电脑音量等等,后面再具体介绍吧。
为了以后方便使用,我们把这些代码封装起来,供以后调用方便。
import cv2
import mediapipe as mp
import time
class HandDetector():
#初始化一些必要参数
def __init__(self,mode=False,maxHands=2,modelComplexity=1,detection=0.75,tracking=0.5):
self.mode=mode
self.maxHands=maxHands
self.modmodelComplexitye=modelComplexity
self.detection=detection
self.tracking=tracking
self.mpHands = mp.solutions.hands
self.hands = self.mpHands.Hands(mode,maxHands,modelComplexity,detection,tracking)
self.mpDraw = mp.solutions.drawing_utils
self.fingerPoints = [4, 8, 12, 16, 20] # 每根手指的第一个点的编号
#对图像进行识别检测
def findHand(self,img,draw=True):
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
self.result = self.hands.process(imgRGB)
if self.result.multi_hand_landmarks:
for handLms in self.result.multi_hand_landmarks: # 每一个手上有多个点,这些点信息放在handLms里
if draw:
self.mpDraw.draw_landmarks(img, handLms, self.mpHands.HAND_CONNECTIONS) # 绘制手上的点并将点用线连起来
return img
#将一个手上的所有点的坐标和id放到数组里返回
def getPosition(self,img):
handList=[]
if self.result.multi_hand_landmarks:
for handLms in self.result.multi_hand_landmarks:
for id, lm in enumerate(handLms.landmark): # 遍历每个手上的所有点的信息
h, w, c = img.shape # 获取窗口的高和宽
cx, cy = int(lm.x * w), int(lm.y * h) # 算出这个点的中心位置
handList.append([id, cx, cy])
return handList
#根据传进来的id值,给对应点的位置画一个圆
def drawPosition(self,img,handList,ids):
if len(handList)>0:
for point in handList:
if point[0]in ids:
id=point[0]
cv2.circle(img, (handList[id][1], handList[id][2]), 10, (40, 123, 222), cv2.FILLED) # 在这个点的中心位置画圆
def main():
pTime = 0
cap = cv2.VideoCapture(0)
detector = HandDetector()
while True:
success, img = cap.read()
img=detector.findHand(img)
handList=detector.getPosition(img)
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
cv2.putText(img, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (40, 123, 222), 3)
cv2.imshow("Image", img)
cv2.waitKey(1)
if __name__=="__main__":
main()