1、介绍

opencv:

OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。 它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。它同样支持很多的AI功能,我们这里主要用Opencv来进行图像的处理,识别工作则由Mediapipe完成。

mediapipe:

Mediapipe是谷歌的一个开源的框架,它支持许多种常见的AI功能,比如人脸检测,手势跟踪,人体姿态检测等等,这次我们需要使用到的是Mediapipe的手势模型,它是谷歌官方训练并开源的一个用于手势检测的工具,因此我们就不需要自己训练模型,只需要调用这个工具就可以了。

检测原理

Mediapipe的hands检测模块通过训练好的模型,能够检测出人手上的21个关节节点,并返回它们在图像中的位置(三维),将它们在图像中标注并用线条连起来,就能够得到如下完整的手势,通过计算各点间的距离和深度就可以实现简单的手势判别。

2、环境安装:

pip install mediapipe

3、导入opencv、MediaPipe和time库

import cv2

import mediapipe as mp

import time

4、创建手部检测模型

# 从摄像头捕获视频
# 参数为0表示打开计算机内置摄像头我的电脑有点带不动很卡也可以用视频的路径
cap = cv2.VideoCapture(0)
mpHands = mp.solutions.hands
hands = mpHands.Hands() 

其中 cv2.VideoCapture() 内的参数,0表示打开计算机内置摄像头,1表示导入已拍摄好的视频

5、程序思路

导入相关的函数包后,捕获笔记本内置的摄像头(当然通过修改参数也可以改为usb连接的摄像头,比如改成1),然后对函数名进行简化(不然太长了),接着调用hands模块配置识别的参数(包括识别严谨程度,追踪信任程度等等,下面有介绍),然后简化一下用来画点和线的函数,接着可以开始设置点和线的颜色及粗细,然后再将两个变量赋值为0(后面要用来计算每秒帧数),接着写一个简单的读取视频的循环(前面的博客中有这个循环,这里不做过多介绍),然后在循环中需要将BGR图像转化为RGB图像,因为mediapipe默认读入的是RGB,而Opencv是BGR,然后将转化过的图像导入模块中进行识别,接着需要得到视频中每一帧图像的高和宽,调用.shape函数即可,然后判断如果进行模块识别之后识别到了手,那么就循环所得到的坐标,调用draw_landmarks函数画出线和点,然后使用enimerate函数列出数据及其下标(这样才能标注出该关节是第几个点),接着需要用得到的坐标乘以前面用.shape得出来的函数求出真正的x,y坐标并转化为整数(这是因为media的landmark返回的是x,y在图像中的百分比坐标,我们需要乘上宽和高才能得出数值坐标),之后我们就可以应用得到的关节坐标,调用函数在关节旁边画出对应的编号,以及打印出实时的关节坐标,接下来我们用time模块写一个小算法就可以得出每秒帧数并将其写在图像上,最后判断一下,如果按下q键,就终止循环,关闭摄像头)。

6、详细代码如下:

import cv2
import mediapipe as mp
#用于得知当前时间
import time
#捕获摄像头,0一般是笔记本的内置摄像头,1,2,3等等则是接在usb口上的摄像头
cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)
#简化函数名
mpHands = mp.solutions.hands
#配置侦测过程中的相关参数
hands = mpHands.Hands(False,4,1,0.7,0.7)
#画点用的函数
mpDraw = mp.solutions.drawing_utils
#点的样式,#线的样式BGR前一个参数是颜色后一个是粗细
handLmStyle = mpDraw.DrawingSpec(color = (0,0,255),thickness = 5)
#线的样式BGR,#线的样式BGR前一个参数是颜色后一个是粗细
handConStyle = mpDraw.DrawingSpec(color = (0,255,0),thickness = 10)
pTime = 0
cTime = 0
#读取视频的循环
while True:
    #读入每一帧图像
    ret,img = cap.read()
    #如果读取不为空值则显示画面
    if ret:
        #将BGR图像转化为RGB图像因为mediapie需要的是RGB
        imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
        #导入图像进行识别
        result = hands.process(imgRGB)
        #print(result.multi_hand_landmarks)
        #得到图像的高
        imgHeight = img.shape[0]
        #得到图像的宽
        imgWeight = img.shape[1]
        if result.multi_hand_landmarks:

            #循环一遍所有的坐标
            for handLms in result.multi_hand_landmarks:
                #画出点和线
                mpDraw.draw_landmarks(img,handLms,mpHands.HAND_CONNECTIONS,handLmStyle,handConStyle)
                for i,lm in enumerate(handLms.landmark):
                    #将坐标转化为整数
                    xPos = int(imgWeight*lm.x)
                    yPos = int(imgHeight*lm.y)

                    #将手上对应的点的编号打印在图片上
                    cv2.putText(img,str(i),(xPos-25,yPos+5),cv2.FONT_HERSHEY_PLAIN,1,(0,0,255),2)
                    #将坐标打印出来
                    print(i,xPos,yPos)

    #得到当前时间
    cTime = time.time()

    #用1除以播放一帧所用时间就可以得出每秒帧数
    fps = 1/(cTime-pTime)
    #得到这一帧结束时的时间
    pTime = cTime
    #将得到的帧数信息打印在图片上
    cv2.putText(img,f"FPS:{int(fps)}",(30,50),cv2.FONT_HERSHEY_PLAIN,2,(255,0,0),2)
    #展示图片
    cv2.imshow("img", img)
    #如果按下q键则终止循环
    if cv2.waitKey(1) ==ord("q"):
        break
cap.release()

7、函数介绍:

mpHands.Hands(False,4,1,0.7,0.7):配置侦测过程中的相关参数

False表示将要识别的不是一张单独的图片,是视频流,需要加上运动跟踪,如果改为True,则是读取单一图片,这里的4意思是最多识别4只手,这个可以自己看情况设置,1则是精准识别模式(会有一点消耗计算性能,但是影响不大,一般都带的动,改为可以降低计算负担,不过会降低精度)而第一个0.7意思是匹配程度需要大于70%,第二个0.7是追踪手部运动时的匹配程度也要大于70%,但是这两个参数设的越高,就需要越长的识别和判断时间,所以需要结合实际情况设置。

mpDraw.draw_landmarks(img,handLms,mpHands.HAND_CONNECTIONS,handLmStyle,handConStyle):画出点和线

img是传入的要画点和线的图像,handLms是点的坐标,mpHands.HAND_CONNECTIONS是线的类型,这里选的是是手势线条,handLmStyle是点的样式,handConStyle是线的样式。

cv2.putText(img,str(i),(xPos-25,yPos+5),cv2.FONT_HERSHEY_PLAIN,2,(0,0,255),2):将手上对应的点的编号打印在图片上

img是传入的要打印内容的图像,str(i)是要打印的字符内容,cv2.FONT_HERSHEY_PLAIN是一种字体,1是字体的线条粗细,(0,0,255)是颜色设置,2是字体大小。