大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 Create ML 将手势和动作分类
借助 Create ML,您的 app 能够更轻松地理解人类手势的表达。了解如何在 Vision 中构建对手势检测的支持,并使用 Create ML app 和框架训练自定义手势和手部动作分类器。了解收集数据、训练模型并将其与 Vision、Camera 和 ARKit 集成从而创造新奇有趣的 app 体验是何等简单。要了解有关 Create ML 和模型训练相关概念的更多信息,请查看 WWDC20 的“使用 Create ML 构建动作分类器”。另外不要错过“使用 Create ML 框架构建动态 iOS app”以了解如何在 app 中即时地以及在设备上训练模型。
资源
相关视频
WWDC23
WWDC22
WWDC21
WWDC20
-
下载
♪ ♪ 嗨 欢迎来到 《使用Create ML 对手势和动作进行分类》 我是内森.渥特曼 今天加入的还有我的同事 布利特妮.维诺特 和 杰比.帕加列 今天 我们将讨论 手部姿势的分类 但在我们深入研究之前 让我们先谈谈手本身 手有两打以上的骨骼、关节和肌肉 堪称工程奇迹 尽管有这种复杂性 但手是婴儿最早用于 与周围世界互动的工具之一 婴儿在能够说话之前就能通过 简单的手部动作 学习交流的基础知识 在我们学会说话后 手就会继续 在交流中发挥作用 并转而增加重点和表达 过去一年里 为了拉近人们的距离 我们的双手变得 比以往任何时候都更加重要 在2020 年 Vision Framework 引入了手势检测 这让开发人员能识别景框中的手 以及手上的21个可识别关节 这当中的每一个 这是很好的工具 如果你正在 尝试识别手是否存在景框中 但如果你想分类手的动作 这可能是一个挑战 虽然手的表达能力是无限的 但在本环节的其余部分 我想专注于简短的单手姿势 例如停止、安静、和平 和简短的单手动作 例如后退、走开 过来这里 我说的就是手的姿势和动作 那么更具体的定义是什么呢? 一方面 我们的姿势 作为静止图像是有意义的 这两个视频虽然是静止的 但主题的意图仍表达得很清楚 把姿势想象成一个图像 另一方面 我们有一个动作 它需要动态来充分传达意义 这两个动作的含义并不明确 只看这一帧影像是不够的 但是随着一系列帧慢慢出现 比如视频或实况照片 动作的意义就显而易见了 亲切的“你好”和“过来” 在厘清这一点后 我很高兴 在今年介绍两个 新的Create ML模板 “手势分类” 和“手部动作分类” 这些新模板让你训练手势和动作模型时,可以 要么使用Create ML app 要么使用Creat ML框架 这些型号兼容macOS Big Sur及更高版本 还有iOS和iPadOS 14及更高版本
至于今年的新功能 我们增加了 使用Create ML框架在 iOS设备上训练模型的能力 你可以在《通过Create ML框架 构建动态iOS应用》 这个专题中了解更多相关信息 首先 我想谈谈手势分类 它可以让你轻松训练机器学习模型 对Vision Framework侦测到的 手部位置进行分类 由于你负责训练模型 因此你可以定义应用程序 应该分类的姿势 以尽量达到其需求 来看这个训练过模型的简短演示 从一个简单的原型应用程序开始 我能够轻松集成手势分类模型 我的应用程序现在可以对手势 进行分类 并显示相应的表情符号 和分类姿势的置信度 它能分类手势一 还有二 但是你会注意到 它没识别的所有姿势 被归类为背景的一部分 包括张开手掌的姿势 我想添加对它的支持 稍后我将把这个模型 交给我的同事布利特妮 来向你展示如何将 手势分类模型整合到你的应用中 在此之前 我想添加对 张开手掌姿势的支持 这真的很容易 但我们应该先谈谈 模型是如何被训练的 就像所有其他Create ML 项目一样 你可以轻易地 把手势分类器整合到你的应用中 该过程分为三个步骤 收集和分类训练数据 训练模型 将模型整合到你的应用程序中 让我们从收集训练数据开始 对手势分类器来说 你需要的是图像 请记住 姿势跟图像都相当 富有表现力 这些图像应分类到 名称与图像姿势相匹配的文件夹中 在这里 我们有两个姿势需要识别: 一、二 以及一个背景类
背景类包含了所有 你的应用并不在意 姿势识别正确与否的目录 在我的演示中 这包括许多 不是一或二的手势 定义明确的背景类别 帮助你的应用了解 用户什么时候摆出的姿势并不重要 这有两种类型的图像 构成了一个背景类 首先 我们有个 随机分类的手部姿势 都不是你会期望你的应用 能分类的重要姿势 这些姿势应该包括多种肤色 年龄、性别和光照条件 其次 我们会有一组手势 非常类似于 你希望应用程序分类的手势 这些过渡姿势经常发生在 当用户手部运动的姿态 接近你的应用程序在乎的表情的时候 当我手掌呈张开姿势时 请注意我在几个姿势之间转换 这类似于、但不完全是 我希望我的应用程序 对张开手掌的判定 当我之后放下手臂时 也会出现这些姿势 这不是张开手掌独有的 当我举起手臂做出两个姿势时 也会出现相同类型的过渡姿势 当我降低它时也会 所有这些过渡姿势都应该 和随机姿势添加进入背景类 这种组合允许模型 正确区分你的应用程序 在乎的姿势 以及所有其他背景姿势
随着训练数据的收集和分类 该来让Create ML 应用程序训练我们的模型了 让我们来亲自动手吧 先从现有的Create ML项目开始 我在之前的演示中 曾用它来训练过模型 训练结果看起来不错 所以我预期这个模型会表现很好 幸运的是 Create ML 应用程序允许你 在将模型整合到 应用程序之前预览它 在预览标签页上 你会发现 对于手势分类器 我们在此版本中添加了实时预览功能 利用FaceTime摄像头实时预览 来向你显示实时预测 使用实时预览 我们可以验证该模型 是否正确分类了“1”和“2”的手势 我希望它也能正确地 对手掌张开的状态进行分类 但它目前将该姿势分类为 背景类的一部分 在我用来训练这个模型的数据源中 请注意它不包含张开手掌的类别 只有1、2和背景的类别 现在让我们训练一个 支持张开手掌的新模型 首先 我将为此创建一个新的模型源
我有一个数据集 其中包含我想用于 这次训练的张开手掌类别 我会选择这个数据集
来看这个新的数据源 我们会发现它现在包括 手掌张开的条目 以及来自先前数据集的类别 回到模型源 我想加些东西 来扩展训练数据 并使我的模型更加健壮
这就对了 该来按下训练了
在训练开始之前 Create ML需要做一些 初步的图像处理 以及特征提取 我们要CreateML 经过80次迭代的训练 这是个很好的起点 但你可能需要根据你的数据集 调整一下数字 这个过程需要一些时间 幸运的是 我已经训练好了一个模型 现在让我把它抓过来 实时预览显示我们新训练的模型 现在可以正确识别打开手掌的姿势 为了确定 我会验证它 是否继续识别1和2的手势
很容易 不是吗? 我要把这个模型 发给我的同事布利特妮 她会谈论如何将它整合到应用程序中 谢谢内森的模型 你好 我是布利特妮.维诺特 我是Vision Framework 团队的成员 当我第一次学习手势分类时 我立刻想到 我可以用它来用手创造特殊效果 我知道使用CoreML 对手部姿势进行分类 使用Vision来侦测和跟踪手部 这两个技术结合在一起会很完美 让我们来看看能否赋予自己超能力 我已经为这个演示 创建了管道的初稿 让我们检查一下 首先 我们将有一个 提供一连串帧的相机 我们会把每一帧进行 通过Vision请求来侦测 手在景框中的位置和关键点 DetectHumanHandPoseRequest 就是我们正在使用的请求 它会为它在帧中找到的每只手 返回一个 HumanHandPoseObservation 我们要发送到 CoreML手势分类模型的数据 是一个MLMultiArray 和HumanHandPoseObservation 上的属性 称之为keypointsMultiArray 然后我们的手势分类器 将会返回给我们 可能性最高的手部动作标签 这个可能性是基于它的置信度得分 这个标签,我们可以用来 确定app内的操作 现在我们已经讲完了 App高级功能的细节 来看一下代码吧 先看看如何使用Vision 侦测帧中的手部 对我们想要做的 只需要一个 VNDetectHumanHandPoseRequest 的实例 并且我们只需要侦测一只手 所以我们将 maximumHandCount设置为1
如果你设好了maximumHandCount 并且有比帧中所指定的更多的手 则算法将侦测的是景框中 最突出和中央的手 maximumHandCount的默认值为2 我们建议在此处设置修改 以便你不会对后续请求的更新吓到 但如果你总是想选择 你连结的SDK支持的 最新算法 那你不必设置它 另外请注意 我们将通过ARKit 对ARSession 检索到的每一帧进行侦测 但这只是从相机来源中 抓取帧的一种方法 你可以使用任何喜欢的方法 AVCaptureOutput 也是一个有用的选择 对接收到的每一帧 我们需要创建一个 VNImageRequestHandler 它能处理图像上的所有请求 手部姿势请求的结果属性 将会被 VNHumanHandPoseObservations 所填满 而手的最多数目为一只 正如我们之前在请求中指定的那样
如果请求没有侦测到手部姿势 我们就要来排除 当前正在显示的任何影响 否则我们只会有单一的 handObservation
接下来 我们要使用 CoreML模型预测我们的手部姿势 我们不想每帧都做一次预测 因为我们不希望效果渲染是抖动的 每隔一段时间进行一次预测 会带来更流畅的用户体验 当我们要进行预测时 我们首先把MLMultiArray 传递到手势CoreML模型 然后从返回的单一预测中 检索顶部标签和置信度 我只想在以高置信度预测标签时 触发对显示效果的更改 这也是关键 能防止 行为可能会开关得太快 或是抖动 在这里 背景分类通过 保持高度置信阈值来帮助我们
如果1的手势的预测信心很大 我们可以设置 effectNode来渲染 但如果1的手势的预测信心不大 那我想停止屏幕上的效果 来配合我的手的动作 让我们来测试一下结果 如果我用手比出一的姿势 它应该会触发单一的能量束特效 很酷吧! 模型能看出我做了手指比1的姿势 并触发了特效 虽然如果它能跟着我的手指会更酷 而如果它在我手指上的 特定点呈现 那就更好了 让我们回去修改代码 我们需要做的是 将手部的关键点位置 馈送到图形资产中 这代表使用视图将标准化的关键点 平移到相机视图空间里 你可能也需要考虑 通过查看置信度分数 来修剪你保存的关键点 在这里 我只关心食指尖 我们得将关键点 平移到坐标空间 因为Vision使用标准化坐标 此外 Vision的原点 位于图像的左下角 所以在进行转换时请记住这点 最后 我们保存索引位置 如果没有找到关键点 我们默认为空值 我们看一下 负责渲染效果的代码 以及我如何把它 调整为跟随我的手指 我们要找到图形对象的位置 是在那里设定 setLocationForEffects 在每一帧 都会被异步调用 默认情况下 我们会让效果出现在 视图的中心 将其切换为之前的 indexFingerTipLocation CGPoint 我们就能获得预期的效果
太棒了! 看起来越来越酷了 让我们再迈出一步 为了创造一个充斥着超能力的 更好玩的图像故事 再多加利用几个 应用程序中的手势分类 会很不错 在这种情况下 我们要选择 2的手势和手掌张开的分类 我已经扩展了我的应用程序 让它在侦测到这两个姿势时采取行动 你看 我让能量束集中 出现在我的食指尖 就像之前的1的手势那样 两个能量束出现在 我的中指和食指尖端 这是2的手势 而最后的能量束是由 手掌张开手势所触发的 并锚定在我中指底部 和手腕关键点 之间的一个关键点
好的 内森和我介绍的所有内容 都涵盖了完全整合 手势分类模型的步骤 另外Vision中还有一项新功能 你可能会发现它很有帮助 所以让我向你介绍一个可能有助于 触发和控制此应用程序功能的API Vision正在引入了这个新属性 来允许用户 在VNHumanHand-PoseObservation上 区分左手和右手:也就是“手性” 这个列举表明了 VNHHumanHandPoseObservation 最有可能是哪只手 并且可以是三个值之一: 左手、右手 以及未知 你大概可以猜到 前两个值背后的含义 但只有在旧版本的 HumanHandPoseObservation 被反序列化 并且从未设置过该属性时 才会出现未知值 正如内森之前提到的 想要获取更多有关 Vision手部姿势侦测信息的话 请回顾WWDC2020的讲座 《通过Vision侦测 身体和手部姿势》 顺带一提 对于在帧中 侦测到的每只手 底层算法将会尝试 分别预测每只手的掌性 这意味着一只手的预测 不会影响 同一帧中对其他手的预测 让我向你展示一个 使用手性的代码会长什么样子 我们已经介绍了创建和运行 VNDetectHumanHandPoseRequest 的设置 在执行请求后 观察将会具有 列举属性的手性 你可以使用它 对Vision的手部姿势观察 采取行动或进行排序
到目前为止 一切都是有关如何使用 手势的分类 但正如内森之前提到的 手部动作分类 是今年的另一项新技术 这里交给杰比与你谈谈 谢谢 布利特妮 你好 我叫杰比帕加列 我是一名机器学习工程师 来自Create ML团队 除了手势分类 今年Create ML还引进了 一个执行手部动作分类的新模板 我会展示如何 在你的应用程序中使用它 也因此 我会通过一些手势 来扩展布利特妮的超能力演示 并点出手部姿势和手部动作 之间的一些重要区别
请参考讲座 《使用Create ML 构建动作分类器》 来自WWDC2020 来获取更多信息和比较结果 因为手部动作和身体动作 是两个非常相似的任务 但现在 让我解释一下 什么是手部动作
手部动作是由在运动过程中 需要经由ML模型分析的 一系列手部姿势所组成 该序列中的姿势数量 应该要大到足以捕捉 从头到尾的整个手部动作
你使用了视频来捕捉手部动作 训练手部动作分类器的方法 与训练手势分类器相同 正如内森之前向我们展示的那样 只有一些细微的差别
静态图像表示手部姿势 而视频则用于捕捉和表示手部动作 所以要训练一个手部动作分类器 你所使用的短视频 其中每个视频都代表了手部动作 这些视频可以被整理成文件夹 其中每个文件夹名称 代表一个动作类别
也别忘了要包括一个 有着动作不同的视频的背景类别 而不是你希望分类器识别的动作
作为替代表示法 你可以把所有范例视频文件 添加到一个文件夹里
然后 使用CSV或 JSON格式来添加注释文件
注释文件中的每个条目代表 视频文件的名称、关联的类别 手部动作的开始和结束时间
此外 在这种情况下 记得背景的类别也要包括在内
记住 多少都要使用相同长度的视频 来训练模型 实际上 你提供的动作持续时间 会作为训练参数 然后Create ML会根据 你所提供的值 它的连续帧数来做随机取样 你也可以提供视频帧率 和训练迭代 除此之外 该应用程序还提供了 不同类型的数据增强 这将有助于模型 更好地概括并提高其准确性 特别是时间插值和丢帧 这是手部动作分类内新增的两个增强 这能提供更接近实例的视频变化
所以我为了演示 训练好了一个 手部动作分类器 来看看它的实际效果吧 好 既然我是超级英雄 我需要一些能量来源
就在这里 你看 我使用手势 来使我的能量来源具象化 但是现在 我要用我的超能力来激活它
在这种情况下 我会使用手部动作 这很酷
现在 手部姿势和手部动作分类器 正在同时执行 我正在善用 Vision新的“手性”功能 并用我的左手来摆姿势 用我的右手做动作
这太酷了 所以这是可行的 这多亏了CreateML 所采取的优化 对每个模型的训练时间释放了 Apple神经引擎的所有力量
现在 让我回到现实世界 向你解释如何将 Create ML手部动作分类器 整合到我的演示中
先来看看模型的输入 当你把手部动作分类器时 整合进入你的应用时 你需要确保 你为模型提供了 正确数量的预期手势 我的模型需要一个大小为 45乘3乘21的MultiArray 因为我可以在XCode预览中检查 你看 45是分类器需要分析 才能识别动作的姿势数 Vision Framework 为每只手提供的关节数是21个 最后 3是x和y坐标 以及每个关节的置信度值 那45是从哪来的? 那是预测窗口的大小 取决于训练时使用的视频的 长度和帧率
就我而言 我决定使用 以每秒30帧录制的1.5秒视频 来训练我的模型 这意味着模型在每个手部动作中 会使用45格 来进行训练 因此在推断过程中 该模型会期待相同数量的手部姿势 另外须额外考虑的是 就是在推断时间 手部姿势到达的频率 重要的是 在推断过程中 呈现给模型的手部姿势的速率 必须要配合用于 训练模型的姿势的速率 在我的演示中 我使用了ARKit 所以我不得不将 每秒抓取的姿势数量减半 因为ARKit提供每秒60帧 而我的分类器是用 每秒30帧的视频训练的 如果不这样做 分类器 可能会提供错误的预测
现在让我们进入源代码 向你展示如何实现这一点 首先 我会使用计数器 将Vision姿势的速率 从每秒60帧降低每秒到30帧 来匹配我预期模型 能够顺利工作的帧率
然后 我得到包含场景中 每只手的关节和手性的数组 接下来 我丢弃左手的关键点 因为在我的演示中 我使用右手 来激活一些用手部动作的效果
好的 现在我需要为分类器 积累手部姿势 为了做到这点 我使用FIFO队列 并累积了45个手部姿势 并确保队列始终包含 最后的45个姿势 队列在最初会是空的 当一个新的手部姿势到来时 我会把它添加到队列中 并重复此步骤直到队列已满 一旦队列已满 我就能开始读取它的全部内容 每当我从Vision收到新的 手部姿势时 我都可以读取队列 但请记得 我正在处理的是 每秒30帧 根据使用的情况 这可能会是在浪费资源
所以我在定义帧数后 会使用 另一个计数器来读取队列
你应该选择队列采样率 作为应用程序响应性与 以及你预期获得的每秒预测数量 两者之间的权衡
此时 我读取了 于MLMultiArray中整个 整理好的45个手部姿势的序列 并将其输入分类器来预测手部动作
然后 我提取预测标签 和置信度值 最后 如果置信度值 大于我们定义的阈值 我会将粒子效果添加到场景中 所以请记得 当你把Create ML 手部动作分类器整合到 你的应用时 请确保以模型期望的帧速率 来输入手势序列 将用于训练分类器的视频 匹配至相同的帧率 使用先进先出队列 收集模型预测的手部姿势 以正确的帧率来读取队列 我期待看到你将使用手部动作模型 来构建所有很酷的应用程序 并使用Create ML进行训练 现在 交给内森 进行最后的总结和回顾 谢谢你 杰比 你和布利特妮 在应用上做得非常好 我很想试一下 但在事情失控之前 你应该记住以下几点 以确保为你的用户提供高质量的体验 请注意手与摄像头的距离 距离应保持在11英尺 或3米半以下 来获得最佳效果 最好避免极端的光照条件 不管是太暗或太亮 笨重、松散或五颜六色的手套 会导致手部姿势难以准确侦测 这可能会影响分类质量 与所有机器学习任务一样 训练数据的质量和数量是关键 对于本还节中展示的手势分类器 我们在每类别使用了500张图像 至于手部动作分类器 我们每个类使用了100部视频 但你的情况的数据要求 可能会有所不同 最重要的是 你要收集 足够的训练数据 来捕捉你的模型 将于应用中所见的预期变化 现在也是时候来回顾了 所以我们学到了什么? 好吧 从2021年起 你可以打造 能够解读人手表情的应用程序 我们讨论了两种范畴的手部表情 姿势和动作之间的差异 我们讲了如何准备训练数据 包括在Create ML应用程序中 用于训练模型的背景分类 我们讨论了如何将训练过的模型 整合到应用程序中 最后 我们讨论了将多个模型 合并到一个应用程序中 并使用掌性来区分是哪只手 当然 今天的演示 只不过是触及了皮毛 Vision Framework 是一种强大的技术 能用于侦测手部的出现 姿势、位置和掌性 Create ML是个有趣简单的方法 能帮助训练及分类 手部姿势和手部动作 当它们一起运作时 将会为人类最强大的 也最具表现力的工具 注入深刻的见解 我们等不及要看到 你们能用它们做什么了 再见 [轻快音乐]
-
-
9:31 - Detecting hands in a frame
func session(_ session: ARSession, didUpdate frame: ARFrame) { let pixelBuffer = frame.capturedImage let handPoseRequest = VNDetectHumanHandPoseRequest() handPoseRequest.maximumHandCount = 1 handPoseRequest.revision = VNDetectHumanHandPoseRequestRevision1 let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]) do { try handler.perform([humanBodyPoseRequest]) } catch { assertionFailure("Human Pose Request failed: \(error)") } guard let handPoses = request.results, !handPoses.isEmpty else { // No effects to draw, so clear out current graphics return } let handObservation = handPoses.first
-
11:03 - Predicting hand pose
if frameCounter % handPosePredictionInterval == 0 { guard let keypointsMultiArray = try? handObservation.keypointsMultiArray() else { fatalError() } let handPosePrediction = try model.prediction(poses: keypointsMultiArray) let confidence = handPosePrediction.labelProbabilities[handPosePrediction.label]! if confidence > 0.9 { renderHandPoseEffect(name: handPosePrediction.label) } } func renderHandPoseEffect(name: String) { switch name { case "One": if effectNode == nil { effectNode = addParticleNode(for: .one) } default: removeAllParticleNode() } }
-
12:25 - Getting tip of index finger to use as anchor
let landmarkConfidenceThreshold: Float = 0.2 let indexFingerName = VNHumanHandPoseObservation.JointName.indexTip let width = viewportSize.width let height = viewportSize.height if let indexFingerPoint = try? observation.recognizedPoint(indexFingerName), indexFingerPoint.confidence > landmarkConfidenceThreshold { let normalizedLocation = indexFingerPoint.location indexFingerTipLocation = CGPoint((x: normalizedLocation.x * width, y: normalizedLocation.y * height)) } else { indexFingerTipLocation = nil }
-
15:47 - Getting hand chirality
// Working with chirality let handPoseRequest = VNDetectHumanHandPoseRequest() try handler.perform([handPoseRequest]) let detectedHandPoses = handPoseRequest.results! for hand in detectedHandPoses where hand.chirality == .right { // Take action on every right hand, or prune the results }
-
22:16 - Hand action classification by accumulating queue of hand poses
var queue = [MLMultiArray]() // . . . frameCounter += 1 if frameCounter % 2 == 0 { let hands: [(MLMultiArray, VNHumanHandPoseObservation.Chirality)] = getHands() for (pose, chirality) in hands where chirality == .right { queue.append(pose) queue = Array(queue.suffix(queueSize)) queueSamplingCounter += 1 if queue.count == queueSize && queueSamplingCounter % queueSamplingCount == 0 { let poses = MLMultiArray(concatenating: queue, axis: 0, dataType: .float32) let prediction = try? handActionModel?.prediction(poses: poses) guard let label = prediction?.label, let confidence = prediction?.labelProbabilities[label] else { continue } if confidence > handActionConfidenceThreshold { DispatchQueue.main.async { self.renderer?.renderHandActionEffect(name: label) } } } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。