圣诞纯情手势告白(Mediapipe基本使用&手势识别详解)

1,600 阅读7分钟
「时光不负,创作不停,本文正在参加2021年终总结征文大赛

前言

本来不想写这个的,但是转了一圈国内mediapipe 的比较少教程。没有那么全面,所以这边还是记录一下吧。

环境安装

如果你是 Anconada 那么你不需要安装,但如果不是,你只需要输入以下指令

pip install mediapipe

再次之前你必须掌握 python3 pyharm 使用 opencv 基本使用

快速上手(手势捕捉)

这边我们举一个快烂大街的东西。

import mediapipe as mp
import cv2


cap = cv2.VideoCapture(0)

mpHand = mp.solutions.hands #mp的手部捕捉
Hand = mpHand.Hands() #找到图片当中的手
mphanddraw = mp.solutions.drawing_utils #绘制工具

while True:
    flag,img = cap.read()


    RGBImage = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #把图片进行转换
    result = Hand.process(RGBImage)

    if(result.multi_hand_landmarks): #如果有手,那么就会得到手的列表记录了手的坐标

        for handlist in result.multi_hand_landmarks:
            mphanddraw.draw_landmarks(img,handlist,mpHand.HAND_CONNECTIONS)
            #绘制手的关节 HAND_CONNECTIONS 把点连接起来

    cv2.imshow("Hands",img)

    if(cv2.waitKey(1)==ord("q")):
        break

cap.release()
cv2.destroyAllWindows()

在这里插入图片描述 注释应该说得很清楚了,这里就还不阐述了。

获取手的坐标

从刚才的效果你可以发现,handlist 包含了我们的一只手的完整坐标,并且可以绘制21个点 那么事实上 handlist 就是21个点的编号和坐标(这个坐标是按照百分比来算的),每个点对应下面的图 在这里插入图片描述

所以我们可以非常清楚的去捕捉到我们手的状态。 接下来我们来对我们的手进行重新绘制。 在这里插入图片描述

import mediapipe as mp
import cv2


cap = cv2.VideoCapture(0)

mpHand = mp.solutions.hands #mp的手部捕捉
Hand = mpHand.Hands() #找到图片当中的手
mphanddraw = mp.solutions.drawing_utils #绘制工具

while True:
    flag,img = cap.read()


    RGBImage = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #把图片进行转换
    result = Hand.process(RGBImage)

    if(result.multi_hand_landmarks): #如果有手,那么就会得到手的列表记录了手的坐标

        for handlist in result.multi_hand_landmarks:

            for id,lm in enumerate(handlist.landmark):
                h,w,c = img.shape
                cx,cy = int(lm.x * w),int(lm.y * h)
                cv2.circle(img,(cx,cy),15,(255,0,255),cv2.FILLED)
            mphanddraw.draw_landmarks(img,handlist,mpHand.HAND_CONNECTIONS,)
            #绘制手的关节 HAND_CONNECTIONS 把点连接起来

    cv2.imshow("Hands",img)

    if(cv2.waitKey(1)==ord("q")):
        break

cap.release()
cv2.destroyAllWindows()

之后的话我们可以对代码进行优化,我们知道我们可以直接获取一个手掌的每一个关节的坐标,在我们的图片上,这样一来我们就可以对我们手的姿态进行预测,判断。这样一来就好玩了。 例如这样: 在这里插入图片描述

import mediapipe as mp
import cv2
import math

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

mpHand = mp.solutions.hands #mp的手部捕捉
Hand = mpHand.Hands() #找到图片当中的手
mphanddraw = mp.solutions.drawing_utils #绘制工具

while True:
    flag,img = cap.read()


    RGBImage = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #把图片进行转换
    result = Hand.process(RGBImage)

    if(result.multi_hand_landmarks): #如果有手,那么就会得到手的列表记录了手的坐标

        hands_data = result.multi_hand_landmarks

        for handlist in hands_data:
            h, w, c = img.shape


            shizhi_postion = (int(handlist.landmark[8].x*w),int(handlist.landmark[8].y*h))
            muzhi_postion = (int(handlist.landmark[4].x * w), int(handlist.landmark[4].y * h))
            cv2.line(img, muzhi_postion, shizhi_postion, (255, 0, 0), 5)

            location = int(shizhi_postion[0]-muzhi_postion[0])**2\
            + int(shizhi_postion[1]-muzhi_postion[1])**2

            location = int(math.sqrt(location))

            showpostion = (int(((muzhi_postion[0]+shizhi_postion[0])/2)) ,int(((muzhi_postion[1]+shizhi_postion[1])/2)))

            cv2.putText(img, str(location), showpostion, cv2.FONT_HERSHEY_PLAIN, 1, (255, 0, 255), 1)

            for id,lm in enumerate(handlist.landmark):

                cx,cy = int(lm.x * w),int(lm.y * h)

                # 我们标注出大拇指和食指的距离

                cv2.circle(img,(cx,cy),15,(255,0,255),cv2.FILLED)
                cv2.putText(img,str(id),(cx,cy),cv2.FONT_HERSHEY_PLAIN,1,(0,255,255),1)



            mphanddraw.draw_landmarks(img,handlist,mpHand.HAND_CONNECTIONS,)
            #绘制手的关节 HAND_CONNECTIONS 把点连接起来

    cv2.imshow("Hands",img)

    if(cv2.waitKey(1)==ord("q")):
        break

cap.release()
cv2.destroyAllWindows()

所以就好玩了。

返回参数详解

这个返回参数很重要,看前面的例子是吧。 首先 hands_data 是多只手的总体坐标,几只手在屏幕上,那么 len()就是几

handlist 是包含了一只手的21个点

handlist 包含了 [{点1},{点2}...] 所以么获取 拇指头就是 handlist.landmark[4].x * w 所以就ok了。

不同算子

这个其实我也不太好说那玩意叫啥,这里的话我先叫他算子。 那么这个有啥用呢,其实就是使用不同的算法模型,来帮助我们提取不同的特征,以便处理。 在这里插入图片描述

这里可以看到这里有很多不同的算子。 调用方式都是类似的,但是处理的方式可能有些许不同。 当然我们这里还是先探讨关于 hands这个东西改怎么玩。后面我们再来说说其他的玩意,到本系列最后说不定可以做一个手势系统,用我们身体的姿态来控制电脑,例如我们可以使用我们的手来充当我们的鼠标(右手)等等,那玩意应该会比较酷!或者我们结合VR游戏驱动,我们将不需要游戏手柄,只需要一个像素良好的摄像头,并且由于 mediapipe 是直接在CPU当中运行的,这就意味着我们可以在没有显卡 GPU 的设备运行,Google官方也说过这玩意可以用到移动端,linux等。

ok 继续,关于前面的代码,我们其实已经可以来做一个简单的音量控制器了,我们只需要对手指间距进行换算即可。不够这个任然不是我们的主题,我们今天的主题其实是如何识别出我们的手势,例如识别 1 ~ 5。

手势识别案例

OK ,那么接下来开始我们的案例,这个案例其实就是 Opencv 快速使用(基础使用&手势识别)在这里插入图片描述 部分,当时这个部分是直接copy的,结果发现这哥们的其实是抄我发的那个视频的代码(吐槽一波写的真烂,有很严重的 OOM 问题。所以我打算自己写一个算了,反正这个挺好玩的)

手指状态判断

首先我们不难发现其实,其实对比官方给出的手指图片以及我前面的那个手指的图片。我们只需要对比关节的坐标就可以判断手有没有立起来。

在这里插入图片描述 相当直观是吧。 但是这里其实还有个问题,那就是大拇指的问题。 由于大拇指太 短了 所以没办法这样直接判断,所以我们需要去使用x 的坐标来判断我们的手。但是这样一来又出现问题了,那就是我们左右手的构造有点不一样。 在这里插入图片描述 所以我们有时候得去判断一下我们那个手是左手还是右手

判断左右手的方式相当简单,看 1 5 号 X 的相对位置就好了。但是还有个问题那就是手心手背的问题但是其实手心手背的投影和那个左右手的投影是一样的。

编码

import mediapipe as mp
import cv2
import math

cap = cv2.VideoCapture(0)

mpHand = mp.solutions.hands #mp的手部捕捉
Hand = mpHand.Hands() #找到图片当中的手
mphanddraw = mp.solutions.drawing_utils #绘制工具


TipsId = [4,8,12,16,20] #定点的坐标
while True:
    flag,img = cap.read()

    RGBImage = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #把图片进行转换
    result = Hand.process(RGBImage)

    if(result.multi_hand_landmarks): #如果有手,那么就会得到手的列表记录了手的坐标

        hands_data = result.multi_hand_landmarks

        for handlist in hands_data:
            h, w, c = img.shape

            fingers = []

            #判断大拇指的开关
            if(handlist.landmark[TipsId[0]-3].x < handlist.landmark[TipsId[0]+1].x):
                if handlist.landmark[TipsId[0]].x >  handlist.landmark[TipsId[0]-1].x:
                    fingers.append(0)
                else:
                    fingers.append(1)
            else:
                if handlist.landmark[TipsId[0]].x < handlist.landmark[TipsId[0] - 1].x:
                    fingers.append(0)
                else:
                    fingers.append(1)
            # 判断其他手指
            for id in range(1,5):
                if(handlist.landmark[TipsId[id]].y > handlist.landmark[TipsId[id]-2].y):
                    fingers.append(0)
                else:
                    fingers.append(1)
            # 获得手指个数
            totoalfingle = fingers.count(1)
            cv2.putText(img,str(totoalfingle),(50,50),cv2.FONT_HERSHEY_PLAIN,
                        5,(255,255,255),5)

            # 这个只是绘制手指关节的,可以忽略这段代码
            for id,lm in enumerate(handlist.landmark):

                cx,cy = int(lm.x * w),int(lm.y * h)
                cv2.circle(img,(cx,cy),15,(255,0,255),cv2.FILLED)
                cv2.putText(img,str(id),(cx,cy),cv2.FONT_HERSHEY_PLAIN,1,(0,255,255),1)



            mphanddraw.draw_landmarks(img,handlist,mpHand.HAND_CONNECTIONS,)


    cv2.imshow("Hands",img)

    if(cv2.waitKey(1)==ord("q")):
        break

cap.release()
cv2.destroyAllWindows()

效果 在这里插入图片描述

升级版(圣诞表白器)

接下来到此我们应该就已经结束了,但是突然想起来那啥,我们其实不仅可以显示文字在我们的文字,还可以叠加图片, SO MAYBE IT CAN WORK SOME INTERESTING THINGS SUCH AS CONFESS TO SOMEBODY WHICH I CAN NOT USE IT RIGHT NOW! FUCK,叫我狗粮制造机,谢谢! 效果 请添加图片描述 代码

import mediapipe as mp
import cv2
import os

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)
mpHand = mp.solutions.hands #mp的手部捕捉
Hand = mpHand.Hands() #找到图片当中的手
mphanddraw = mp.solutions.drawing_utils #绘制工具

MediaPath = "Media"
picsdir = os.listdir(MediaPath)
pics = []
for pic in picsdir:
    img = cv2.imread(f"{MediaPath}/{pic}")
    pics.append(img)




TipsId = [4,8,12,16,20] #定点的坐标
while True:
    flag,img = cap.read()

    RGBImage = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #把图片进行转换
    result = Hand.process(RGBImage)

    if(result.multi_hand_landmarks): #如果有手,那么就会得到手的列表记录了手的坐标

        hands_data = result.multi_hand_landmarks

        for handlist in hands_data:
            h, w, c = img.shape

            fingers = []

            #判断大拇指的开关
            if(handlist.landmark[TipsId[0]-2].x < handlist.landmark[TipsId[0]+1].x):
                if handlist.landmark[TipsId[0]].x >  handlist.landmark[TipsId[0]-1].x:
                    fingers.append(0)
                else:
                    fingers.append(1)
            else:
                if handlist.landmark[TipsId[0]].x < handlist.landmark[TipsId[0] - 1].x:
                    fingers.append(0)
                else:
                    fingers.append(1)
            # 判断其他手指
            for id in range(1,5):
                if(handlist.landmark[TipsId[id]].y > handlist.landmark[TipsId[id]-2].y):
                    fingers.append(0)
                else:
                    fingers.append(1)
            # 获得手指个数,绘制图片
            totoalfingle = fingers.count(1)

            coverpic = pics[totoalfingle-1]
            hc, wc, cc = coverpic.shape
            img[0:wc,0:hc] = coverpic

            # 这个只是绘制手指关节的,可以忽略这段代码
            for id,lm in enumerate(handlist.landmark):

                cx,cy = int(lm.x * w),int(lm.y * h)
                cv2.circle(img,(cx,cy),15,(255,0,255),cv2.FILLED)

            mphanddraw.draw_landmarks(img,handlist,mpHand.HAND_CONNECTIONS,)


    cv2.imshow("Hands",img)

    if(cv2.waitKey(1)==ord("q")):
        break

cap.release()
cv2.destroyAllWindows()

图片自己准备去(如果有人表白成功了记得踹我) 在这里插入图片描述

在这里插入图片描述

总结

最基本的使用其实就是这样的,后面你想怎么架构,就怎么架构,这个无所谓,按照这个模板来套就可以了。