如何使用Python的MediaPipe HandPose检测

857 阅读7分钟

使用Python的MediaPipe HandPose检测

手势识别是一种深度学习技术,它允许你检测你手上的不同点。你手上的这些点通常被称为地标。这些地标包括手指的关节、尖端和基部。

MediaPipe提供许多可定制的ML预训练模型。handpose模型是他们最新发布的模型之一。随着研究人员旨在利用这种惊人的预训练模型使人工智能民主化,我们只需几行代码就能理解ML,这一点很重要。

本教程旨在向你展示如何使用MediaPipe和Python建立你自己的Handpose检测器。你将能够使用你的电脑的网络摄像头来跟踪你的手的关节。

前提条件

要跟上本教程,你需要熟悉以下内容。

  • 机器学习建模。
  • Jupyter笔记本/谷歌Colab。

我们将在本教程中使用谷歌Colab。

手势模型

handpose模型是由tensorflow.js提供的,可以检测到你手上的21个不同的不同点。MediaPipe Hands模型是一个轻量级的ML管道,由一个手掌检测器和一个手掌骨架手指跟踪模型组成。

最初,手掌检测器检测手的位置,之后,手骨架手指跟踪模型进行精确的关键点定位,预测每个检测到的手的21个3D手的关键点。

让我们看看如何在一个项目中使用这个手骨架模型。

安装和导入依赖项

我们对两个核心依赖项进行快速的pip安装;MediaPipe和openCV Python库。MediaPipe是一个开源的、跨平台的库,为解决计算机视觉问题提供了许多现成的ML解决方案。一些例子包括人脸检测自拍分割头发分割物体检测的ML解决方案。在本教程中,我们利用该库在我们的项目中导入MediaPipe Hands模型。

我们还将安装OpenCV库。与MediaPipe一样,OpenCV也是一个有助于解决计算机视觉问题的库。在本教程中,我们将使用该库来处理图像,并轻松地实时访问我们的网络摄像头。

!pip install mediapipe opencv-python

接下来,我们将把必要的依赖项导入我们的笔记本。

import mediapipe as mp
import cv2
import numpy as np
import uuid
import os
from google.colab.patches import cv2_imshow

我们已经导入了五个依赖项。

  • mediapipe 使我们能够利用MediaPipe ML解决方案。
  • cv2 为我们提供OpenCV。
  • numpy 让我们更容易处理数字输出。
  • uuid 允许你生成一个统一的唯一标识符。
  • os 允许我们在操作系统中与文件一起工作。

我们现在可以设置mediapipe了。让我们引入mediapipe的手部模型和绘图工具,帮助我们画出手上的所有地标。

mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands

现在所有这些都完成了,让我们使用标准的OpenCV代码来设置我们的网络摄像头。

img = cv2.imread('logo.png', cv2.IMREAD_UNCHANGED)
cv2_imshow(img)

在Google Colab中使用OpenCV访问你的网络摄像头并不是很直接,因为你不是在使用你的本地运行时,而是在使用Google Colab运行时。

为了在虚拟机中利用你的本地机器的网络摄像头,你可以复制粘贴以下JavaScript代码到你的Colab中。

from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode

def take_photo(filename='photo.jpg', quality=0.8):
  js = Javascript('''
    async function takePhoto(quality) {
      const div = document.createElement('div');
      const capture = document.createElement('button');
      capture.textContent = 'Capture';
      div.appendChild(capture);

      const video = document.createElement('video');
      video.style.display = 'block';
      const stream = await navigator.mediaDevices.getUserMedia({video: true});

      document.body.appendChild(div);
      div.appendChild(video);
      video.srcObject = stream;
      await video.play();

      // Resize the output to fit the video element.
      google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

      // Wait for Capture to be clicked.
      await new Promise((resolve) => capture.onclick = resolve);

      const canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext('2d').drawImage(video, 0, 0);
      stream.getVideoTracks()[0].stop();
      div.remove();
      return canvas.toDataURL('image/jpeg', quality);
    }
    ''')
  display(js)
  data = eval_js('takePhoto({})'.format(quality))
  binary = b64decode(data.split(',')[1])
  with open(filename, 'wb') as f:
    f.write(binary)
  return filename

这段代码是由谷歌的团队预先建立的,以帮助开发人员更容易访问他们的网络摄像头。

此外,还可以复制粘贴下面的代码。运行这段代码将打开你电脑的网络摄像头。一旦它启动,你就可以捕捉到自己的图像。记住要用你的一只手来捕捉你的图像,因为这个模型的目的是捕捉手部姿势。如果你不捕捉你的手,你将不会看到任何结果。该图像将被保存为photo.jpg

from IPython.display import Image
try:
  filename = take_photo()
  print('Saved to {}'.format(filename))
  
  # Show the image which was just taken.
  display(Image(filename))
except Exception as err:
  # Errors will be thrown if the user does not have a webcam or if they do not
  # grant the page permission to access it.
  print(str(err))

现在让我们把mediapipe的手部模型覆盖在标准的OpenCV代码之上。我们将从我们的网络摄像头获取信息,将其传递给mediapipe,进行检测,并将结果渲染到图像上。因此,我们不仅会得到网络摄像头的反馈,而且会得到网络摄像头的反馈,并将所有这些检测结果应用于其中。

使用网络摄像头拍摄的图像来检测手势

在我们将手放在摄像头前的捕捉到的图像中,我们应该看到我们手上的所有关节都被检测到了。

cap = cv2.imread('photo.jpg', cv2.IMREAD_UNCHANGED)

with mp_hands.Hands(min_detection_confidence=0.8, min_tracking_confidence=0.5) as hands: #You can pass `max_num_hands` argument here as well if you want to detect more that one hand
        
        image = cv2.cvtColor(cap, cv2.COLOR_BGR2RGB)
        
        image.flags.writeable = False
        
        results = hands.process(image)
        
        image.flags.writeable = True
        
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
       
        print(results)
        
        # Rendering results
        if results.multi_hand_landmarks:
            for num, hand in enumerate(results.multi_hand_landmarks):
                mp_drawing.draw_landmarks(image, hand, mp_hands.HAND_CONNECTIONS, 
                                         )
            
        cv2.imwrite(os.path.join('Output Images', '{}.jpg'.format(uuid.uuid1())), image)
        cv2_imshow(image)

cv2.destroyAllWindows()

我们首先使用以下代码实例化我们的模型with mp_hands.Hands(min_detection_confidence=0.8, min_tracking_confidence=0.5) as hands 。我们传入两个关键字参数,min_detection_confidence ,将检测置信度设置为80%,min_tracking_confidence ,将跟踪置信度设置为50%。

然后,我们使用OpenCV的cvtColor 方法将我们的框架从BGR 重新着色到RGB 。默认情况下,OpenCV将图像颜色的格式设置为BGR 。我们需要将其设置为RGB ,因为那是mediapipe接受的格式。我们将这个结果存储在一个叫做image 的变量中。

我们将我们的可写标志,image.flags.writeable 最初设置为false ,以避免在我们进行检测后再将其设置为true 。代码hands.process(image) 继续进行我们的检测,并将其存储在一个被称为results 的变量中。

下一步是使用cvtColor 方法。我们将图像的颜色从RGB 设置回BGR ,并打印我们的检测结果。在这一点上,我们的网络摄像头图像没有发生任何变化。如果我们在终端上输入results.multi_hand_landmarks ,我们应该看到手势检测结果。我们需要将这些检测结果渲染到我们的图像上。

代码的最后一点帮助我们将这些结果渲染到我们的图像上。如果我们在multi_hand_landmarks 中有结果,就渲染图像,如果没有,就不渲染。然后,我们循环浏览每一组结果,并绘制地标。mp_hands.HAND_CONNECTIONS ,告诉我们关系的集合;哪些地标与哪些地标相连,让我们绘制连接。

默认情况下,mediapipe检测到的最多是两只手。如果你想检测一个以上的人的手,你必须引入一个参数,max_num_hands ,并指出你想检测的手的数量。它默认设置为两只,以减少延迟,因为如果没有其他的手要被检测,就没有必要调用另一个检测。

这就是了。如果你运行网络摄像头画面并展示你的手,你应该在你的手指上有地标检测。

使用OpenCV输出图像

最后,如果你想保存检测的结果,也许是为了研究论文或个人使用,你可以通过添加以下代码来实现。

          cv2.imwrite(os.path.join('Output Images', '{}.jpg'.format(uuid.uuid1())), image)

上面这行代码是要保存我们的图像。唯一的标识符,format(uuid.uuid1() ,为我们检测到的图像生成唯一的名称,以避免在保存图像时发生命名冲突。

收尾工作

我们从安装和导入我们的依赖项开始,我们从我们的网络摄像头进行检测,并将这些检测应用于我们的网络摄像头馈送。最后一步是保存我们的输出。你可以自己去试试。