[译] 在浏览器里使用 TenserFlow.js 实时估计人体姿态

2,281 阅读15分钟

发布者:Dan Oved,谷歌创意实验室自由创意技术专家,纽约大学 ITP 研究生。 编辑和插图:创意技术专家 Irene Alvarado 和谷歌创意实验室自由平面设计师 Alexis Gallo

在与谷歌创意实验室的合作中,我很高兴地宣布 TensorFlow.jsPoseNet¹ 的发行,² 它是一个能够在浏览器里对人体姿态进行实时估计的机器学习模型。点击这里在线体验。

PoseNet 使用单人姿态或多人姿态算法可以检测图像和视频中的人物形象 —— 全部在浏览器中完成。

那么,姿态估计究竟是什么呢?姿态估计是指在图像和视频中检测人物的计算机视觉技术,比如,可以确定某个人的肘部在图像中的位置。需要澄清一点,这项技术并不是识别图像中是 —— 姿态估计不涉及任何个人身份信息。该算法仅仅是估计身体关键关节的位置。

好吧,为什么这是令人兴奋的开始?姿态估计有很多用途,从互动装置反馈身体,到增强现实动画健身用途等等。我们希望借助此模型激发更多的开发人员和制造商尝试将姿态检测应用到他们自己的独特项目中。虽然许多同类姿态检测系统也已经开源,但它们都需要专门的硬件和/或相机,以及相当多的系统安装。 借助运行在 TensorFlow.js 上的 PoseNet任何人只需拥有带摄像头的台式机或者手机即可在浏览器中体验这项技术。而且由于我们已经开源了这个模型,JavaScript 开发人员可以用几行代码来修改和使用这个技术。更重要的是,这实际上可以帮助保护用户隐私。因为基于 TensorFlow.js 的 PoseNet 运行在浏览器中,任何姿态数据都不会离开用户的计算机。

在我们深入探讨如何使用这个模型的细节之前,先向所有让这个项目成为可能的人们致意:论文对户外多人姿态的精确估计PersonLab: 自下而上的局部几何嵌入模型的人体姿态估计和实例分割的作者 George PapandreouTyler Zhu,以及 TensorFlow.js 库背后的 Google Brain 小组的工程师 Nikhil ThoratDaniel Smilkov


PoseNet 入门

PoseNet 可以被用来估计单人姿态多人姿态,这意味着算法有一个检测图像/视频中只有一个人的版本,和检测多人的版本。为什么会有两个版本?因为单人姿态检测更快,更简单,但要求图像中只能有一个主体(后面会详细说明)。我们先说单体姿态,因为它更简单易懂。

姿态估计整体来看主要有两个阶段:

  1. 输入一个通过卷积神经网络反馈的 RGB 图像
  2. 使用单人姿态或多人姿态解码算法从模型输出中解码姿态,姿态置信得分关键点位置,以及关键点置信得分

等一下,所有这些关键词指的是什么?我们看看一下最重要的几个:

  • 姿态 - 从最上层来看,PoseNet 将返回一个姿态对象,其中包含每个检测到的人物的关键点列表和实例级别的置信度分数。

PoseNet 返回检测到的每个人的置信度值以及检测到的每个姿态关键点。图片来源:「Microsoft Coco:上下文数据集中的通用对象」,cocodataset.org

  • 姿态置信度分数 - 决定了对姿态估计的整体置信度。它介于 0.0 和 1.0 之间。它可以用来隐藏分数不够高的姿态。
  • 关键点 —— 估计的人体姿态的一部分,例如鼻子,右耳,左膝,右脚等。 它包含位置和关键点置信度分数。PoseNet 目前检测到下图所示的 17 个关键点:

PosNet 检测到17个姿态关键点。

  • 关键点置信度得分 - 决定了估计关键点位置准确的置信度。它介于 0.0 和 1.0 之间。它可以用来隐藏分数不够高的关键点。
  • 关键点位置 —— 检测到关键点的原始输入图像中的二维 x 和 y 坐标。

第 1 部分:导入 TensorFlow.js 和 PoseNet 库

很多工作都是将模型的复杂性抽象化并将功能封装为易于使用的方法。我们看一下构建 PoseNet 项目的基础知识。

该库可以通过 npm 安装:

npm install @[tensorflow-models/posenet](https://www.npmjs.com/package/@tensorflow-models/posenet)

然后使用 es6 模块导入:

import * as posenet from '@[tensorflow-models/posenet](https://www.npmjs.com/package/@tensorflow-models/posenet)';

const net = await posenet.load();

或通过页面中的一个包:

<html>
  <body>
    <!-- Load TensorFlow.js -->
    <script src="[https://unpkg.com/@tensorflow/tfjs](https://unpkg.com/@tensorflow/tfjs)"></script>
    <!-- Load Posenet -->
    <script src="[https://unpkg.com/@tensorflow-models/posenet](https://unpkg.com/@tensorflow-models/posenet)">
    </script>
    <script type="text/javascript">
      posenet.load().then(function(net) {
        // posenet 模块加载成功
      });
    </script>
  </body>
</html>

第 2a 部分:单人姿态估计

单人姿态估计算法用于图像的示例。图片来源:「Microsoft Coco:上下文数据集中的通用对象」,cocodataset.org

如前所述,单人姿态估计算法更简单,速度更快。它的理想使用场景是当输入图像或视频中心只有一个人。缺点是,如果图像中有多个人,那么来自两个人的关键点可能会被估计成同一个人的姿态的一部分 —— 举个例子,路人甲的左臂和路人乙的右膝可能会由该算法确定为属于相同姿态而被合并。如果输入图像可能包含多人,则应该使用多人姿态估计算法。

我们来看看单人姿态估计算法的输入

  • 输入图像元素 —— 包含要预测图像的 html 元素,例如视频或图像标记。重要的是,输入的图像或视频元素应该是正方形的。
  • 图像比例因子 —— 在 0.2 和 1 之间的数字。默认为 0.50。在送入神经网络之前如何缩放图像。将此数字设置得较低以缩小图像,并以精度为代价增加通过网络的速度。
  • 水平翻转 —— 默认为 false。表示是否姿态应该水平/垂直镜像。对于视频默认水平翻转(比如网络摄像头)的视频,应该设置为 true,因为你希望姿态能以正确的方向返回。
  • 输出步幅 —— 必须为 32,16 或 8。默认 16。在内部,此参数会影响神经网络中图层的高度和宽度。在上层看来,它会影响姿态估计的精度速度。值越小精度越高,但速度更慢,值越大速度越快,但精度更低。查看输出步幅对输出质量的影响的最好方法是体验这个单人姿态估计演示

下面让我们看一下单人姿态估计算法的输出

  1. 一个包含姿态置信度得分和 17 个关键点数组的姿态。
  2. 每个关键点都包含关键点位置和关键点置信度得分。同样,所有关键点位置在输入图像的坐标空间中都有 x 和 y 坐标,并且可以直接映射到图像上。

这个段代码块显示了如何使用单人姿态估计算法:

const imageScaleFactor = 0.50;
const flipHorizontal = false;
const outputStride = 16;

const imageElement = document.getElementById('cat');

// 加载 posenet 模型
const net = await posenet.load();

const pose = await net.estimateSinglePose(imageElement, scaleFactor, flipHorizontal, outputStride);

输出姿态示例如下:

{
  "score": 0.32371445304906,
  "keypoints": [
    { // 鼻子
      "position": {
        "x": 301.42237830162,
        "y": 177.69162777066
      },
      "score": 0.99799561500549
    },
    { // 左眼
      "position": {
        "x": 326.05302262306,
        "y": 122.9596464932
      },
      "score": 0.99766051769257
    },
    { // 右眼
      "position": {
        "x": 258.72196650505,
        "y": 127.51624706388
      },
      "score": 0.99926537275314
    },
    ...
  ]
}

第 2b 部分:多人姿态估计

多人姿态估计算法应用于图像的例子。图片来源:「Microsoft Coco:上下文数据集中的通用对象」,cocodataset.org

多人姿态估计算法可以估计图像中的多个姿态/人物。它比单人姿态算法更复杂并且稍慢,但它的优点是,如果图片中出现多个人,他们检测到的关键点不太可能与错误的姿态相关联。出于这个原因,即使用例是用来检测单人的姿态,该算法也可能更合乎需要。

此外,该算法吸引人的特性是性能不受输入图像中人数的影响。无论是 15 人还是 5 人,计算时间都是一样的。

让我们看一下它的输入

  • 输入图像元素 —— 与单人姿态估计相同
  • 图像比例因子 —— 与单人姿态估计相同
  • 水平翻转 —— 与单人姿态估计相同
  • 输出步幅 —— 与单人姿态估计相同
  • 最大检测姿态 —— 一个整数。默认为 5,表示要检测的姿态的最大数量。
  • 姿态置信分数阈值 —— 0.0 至 1.0。默认为 0.5。在更深层次上,这将控制返回姿态的最低置信度分数。
  • 非最大抑制(NMS,Non-maximum suppression)半径 —— 以像素为单位的数字。在更深层次上,这控制了返回姿态之间的最小距离。这个值默认为 20,这在大多数情况下可能是好的。可以通过增加/减少,以滤除不太准确的姿态,但只有在调整姿态置信度分数无法满足时才调整它。

查看这些参数有什么影响的最好方法是体验这个多人姿态估计演示

让我们看一下它的输出

  • 以一系列姿态为 resolvepromise
  • 每个姿态包含与单人姿态估计算法中相同的信息。

这段代码块显示了如何使用多人姿态估计算法:

const imageScaleFactor = 0.50;
const flipHorizontal = false;
const outputStride = 16;
// 最多 5 个姿态
const maxPoseDetections = 5;
// 姿态的最小置信度
const scoreThreshold = 0.5;
// 两个姿态之间的最小像素距离
const nmsRadius = 20;

const imageElement = document.getElementById('cat');

// 加载 posenet
const net = await posenet.load();

const poses = await net.estimateMultiplePoses(
  imageElement, imageScaleFactor, flipHorizontal, outputStride,    
  maxPoseDetections, scoreThreshold, nmsRadius);

输出数组的示例如下所示:

// 姿态/人的数组
[ 
  { // pose #1
    "score": 0.42985695206067,
    "keypoints": [
      { // nose
        "position": {
          "x": 126.09371757507,
          "y": 97.861720561981
         },
        "score": 0.99710708856583
      },
      ... 
    ]
  },
  { // pose #2
    "score": 0.13461434583673,
    "keypositions": [
      { // nose
        "position": {
          "x": 116.58444058895,
          "y": 99.772533416748
        },
      "score": 0.9978438615799
      },
      ...
    ]
  },
  ... 
]

如果你已经读到了这里,你应该了解的足够多,可以开始 PoseNet 演示了这可能是一个很好的停止点。如果你想了解更多关于该模型和实现的技术细节,我们邀请你继续阅读下文。


写给好奇的人:技术深探

在本节中,我们将介绍关于单人姿态估计算法的更多技术细节。在上层来看,这个过程如下所示:

使用 PoseNet 的单人姿态检测器流程。

需要注意的一个重要细节是研究人员训练了 PoseNet 的 ResNet 模型和 MobileNet 模型。虽然 ResNet 模型具有更高的准确性,但其大尺寸和多层的特点会使页面加载时间和推导时间对于任何实时应用程序都不理想。我们使用 MobileNet 模型,因为它是为移动设备而设计的。

重新审视单人姿态估计算法

处理模型输入:输出步幅的解释

首先,我们将说明如何通过讨论输出步幅来获得 PoseNet 模型输出(主要是热图和偏移矢量)。

比较方便地是,PoseNet 模型是图像大小不变的,这意味着它能以与原始图像相同的比例预测姿态位置,而不管图像是否缩小。这意味着 PoseNet 可以通过设置上面我们在运行时提到的输出步幅以牺牲性能获得更高的精度

输出步幅决定了我们相对于输入图像大小缩小输出的程度。它会影响图层和模型输出的大小。输出步幅越大,网络中的层和输出的分辨率越小,准确性也越低。在此实现中,输出步幅可以为 8,16 或 32。换句话说,输出步幅为 32 将输出最快但是精度最低,而 8 的精度最高但是最慢。我们建议从 16 开始。

输出步幅决定了输出相对于输入图像的缩小程度。较高的输出步幅会更快,但会导致精度较低。

本质是,当输出步幅设置为 8 或 16 时,层中的输入量减少使得可以创建更大的输出分辨率。然后使用 Atrous 卷积来使后续图层中的卷积滤波器具有更宽的视野(当输出步幅为 32 时,不会应用 atrous 卷积)。虽然 Tensorflow 支持 atrous 卷积,但 TensorFlow.js 不支持,所以我们添加了一个 PR 来包含这个。

模型输出:热图和偏移矢量

当 PoseNet 处理图像时,事实上返回的是热图以及偏移矢量,可以解码以找到图像中与姿态关键点对应的高置信度区域。我们将在一分钟内讨论它们各自的含义,但现在下面的插图以高级方式捕获每个姿态关键点与一个热图张量和一个偏移矢量张量的关联。

PoseNet 返回的 17 个姿态关键点中的每一个都与一个热图张量和一个偏移矢量张量相关联,用于确定关键点的确切位置。

这两个输出都是具有高度和宽度的 3D 张量,我们将其称为分辨率。分辨率由输入图像大小和输出步幅根据以下公式确定:

Resolution = ((InputImageSize - 1) / OutputStride) + 1

// 示例:宽度为 225 像素且输出步幅为 16 的输入图像产生输出分辨率为 15
// 15 = ((225 - 1) / 16) + 1

热图

每个热图是尺寸 resolution x resolution x 17 的 3D 张量,因为 17 是 PoseNet 检测到的关键点的数量。例如,图像大小为 225,输出步幅为 16,那么就是 15 x 15 x 17。第三维(17)中的每个切片对应于特定关键点的热图。该热图中的每个位置都有一个置信度分数,这是该类型关键点的一部分在该位置的概率。它可以被认为是原始图像被分解成 15 x 15 网格,其中热图分数提供了每个网格中每个关键点存在概率的等级。

偏移矢量

每个偏移矢量都是尺寸 resolution x resolution x 34 的 3D 张量,其中 34 是关键点数* 2。图像大小为 225,输出步幅为 16 时,它是 15 x 15 x 34。由于热图是关键点位置的近似值,所以偏移矢量在位置上对应于热图中的点,用于遍历热图点对应的向量,从而预测关键点的确切位置。偏移矢量的前 17 个切片包含矢量的 x 坐标和后 17 个包含 y 坐标。偏移矢量大小与原始图像具有相同的比例

根据模型的输出估计姿态

图像通过模型馈送后,我们执行一些计算来从输出中估计姿态。例如,单人姿态估计算法返回姿态置信度分数,它包含关键点数组(以各部分的 ID 作索引),每个关键点具有置信度分数和 x,y 位置。

为了获得姿态的关键点:

  1. 调用热图的 sigmoid 方法,以获得分数。 scores = heatmap.sigmoid()
  2. argmax2d 是根据关键点置信度得分来获得热图中 x 和 y 索引,取每个部分的得分最高者,一般也是该部分最有可能存在的地方。这会产生一个尺寸为 17 x 2 的张量,每一行都是每部分得分最高的热图中的 y 和 x 索引。 heatmapPositions = scores.argmax(y, x)
  3. 每部分的偏移矢量通过从该部分的热图中对应 x 和 y 索引的偏移量中获取 x 和 y。这会产生 17 x 2 的张量,每行都是相应关键点的偏移向量。例如,对于索引为 k 的部分,当热图位置为 y 和 x 时,偏移矢量为: offsetVector = [offsets.get(y, x, k), offsets.get(y, x, 17 + k)]
  4. 为了获得关键点,将每部分的热图 x 和 y 乘以输出步幅,然后将其添加到它们对应的偏移向量中,该向量与原始图像具有相同的比例。 keypointPositions = heatmapPositions * outputStride + offsetVectors
  5. 最后,每个关键点置信度分数是其热图位置的置信度分数。该姿态的置信度得分是关键点的评分的平均值。

多人姿态估计

多人姿态估计算法的细节超出了本文的范围。该算法的主要不同之处在于它使用贪婪过程通过沿着基于部分图的位移矢量将关键点分组为姿态。具体来说,它使用研究论文 PersonLab: 自下而上的局部几何嵌入模型的人体姿态估计和实例分割中的快速贪婪解码算法。有关多人姿态算法的更多信息,请阅读完整的研究论文或查看代码


我们希望随着越来越多的模型被移植到 TensorFlow.js,机器学习的世界变得对更容易被新的开发者和制造者接受,更受欢迎和更有趣。基于 TensorFlow.js 的 PoseNet 是实现这一目标的一个小小尝试。我们很乐意看到你做出了什么 —— 不要忘记使用 #tensorflowjs 和 #posenet 分享您的精彩项目!


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏