Highlight
Create ML 新增 Hand Pose Classification 和 Hand Action Classification 两个模板,开发者可以训练自定义手势分类器,结合 Vision 的手部关键点检测实现手势控制 App。
核心内容
手势表达的两种形态
Vision 框架在 iOS 14 引入了手部关键点检测,能识别画面中的手以及 21 个关节点的位置。但知道手在哪里和知道手在做什么,是两个不同的问题。
WWDC2021 区分了两种手势表达(01:36):
- Pose(姿态):静态姿势,单张图片就能表达完整含义。比如「竖大拇指」、「比耶」、「停止」手势。
- Action(动作):需要连续运动才能表达含义。比如「挥手」、「过来」、「走开」。单看一帧无法判断意图,需要一段视频序列。
针对这两种形态,Create ML 分别提供了 Hand Pose Classification 和 Hand Action Classification 模板。
Hand Pose Classification 训练流程
训练数据需要按类别分文件夹存放(04:08)。每个文件夹名就是一个类别名。必须包含一个 Background 类,放两类图片:一类是各种无关手势的随机照片(要覆盖不同肤色、年龄、光照),另一类是目标手势的过渡姿态(比如手从放下到「停止」手势中间经过的位置)。
Background 类的作用是让模型学会「什么时候不触发」。没有它,模型会把所有手的位置都分类成某个目标手势,导致误触发。
Create ML app 今年新增了 Live Preview 功能(06:07),可以用 FaceTime 摄像头实时测试模型效果,不需要先集成到 App 里。
Hand Action Classification 的特殊要求
动作分类需要视频作为训练数据(17:23)。每个视频代表一个动作,按类别分文件夹存放。同样需要 Background 类,放与目标动作无关的视频。
训练时需要指定 action duration(动作持续时间),Create ML 会从这个时间段内随机采样连续帧。视频帧率和训练时指定的帧率要匹配。演示中用了 30fps、1.5 秒的视频,所以模型期望输入 45 帧(45 个手部姿态)。
Vision 的 Chirality:区分左右手
Vision 今年给 VNHumanHandPoseObservation 新增了 chirality 属性(14:44),可以判断检测到的手是左手还是右手。左右手的判断是独立的,一只手的结果不影响另一只手的判断。
这个特性让「左手控制移动、右手控制攻击」这类游戏交互成为可能。
详细内容
用 Vision 检测手部关键点
(09:31)
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([handPoseRequest])
} catch {
assertionFailure("Hand Pose Request failed: \(error)")
}
guard let handPoses = handPoseRequest.results, !handPoses.isEmpty else {
// 没有检测到手,清除特效
return
}
let handObservation = handPoses.first
}
关键点:
maximumHandCount控制最多检测几只手,默认是 2。如果画面中手更多,算法会选最突出、最居中的手- 建议显式设置
revision,避免 SDK 更新后行为变化 - 这里用 ARKit 获取帧,也可以用
AVCaptureOutput作为替代
手势分类预测
(11:03)
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()
}
}
关键点:
- 不需要每帧都做预测,间隔预测可以避免特效抖动
keypointsMultiArray()把 21 个关节点转换成 MLMultiArray,直接输入 Core ML 模型- 置信度阈值设得高(0.9)可以减少误触发,Background 类的存在让高阈值成为可能
labelProbabilities可以获取所有类别的概率分布
获取食指指尖位置作为特效锚点
(12:25)
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
}
关键点:
recognizedPoint(_:)获取指定关节点的归一化坐标(0-1 范围)- 必须检查
confidence,低置信度的关键点位置不可靠 - Vision 的坐标原点在左下角,和 UIKit 的左上角原点不同,转换时要注意
- 乘以 viewport 宽高把归一化坐标转成屏幕坐标
左右手区分
(15:47)
let handPoseRequest = VNDetectHumanHandPoseRequest()
try handler.perform([handPoseRequest])
let detectedHandPoses = handPoseRequest.results!
for hand in detectedHandPoses where hand.chirality == .right {
// 只对右手采取行动,或过滤结果
}
关键点:
chirality是VNHumanHandPoseObservation.Chirality枚举,有.left、.right、.unknown三个值.unknown只在反序列化旧版本 observation 时出现- 每只手的 chirality 独立判断,不受其他手影响
手部动作分类:累积姿态队列
(22:16)
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)
}
}
}
}
}
关键点:
- 动作分类需要累积一段时序的姿态数据,用 FIFO 队列保存最近
queueSize个姿态 - 演示中 ARKit 输出 60fps,但模型训练用 30fps,所以每 2 帧取 1 帧(
frameCounter % 2 == 0) - 队列满后,按
queueSamplingCount间隔读取,平衡响应速度和计算量 MLMultiArray(concatenating:axis:dataType:)把队列中的姿态拼接成模型期望的输入形状(45x3x21)
核心启发
1. 手势控制 AR 游戏
- 做什么:用不同手势触发不同的 AR 特效或技能,比如「握拳」发射火球、「张开手掌」生成护盾
- 为什么值得做:Hand Pose Classification 只需要几十张训练图片就能达到可用精度,Create ML app 的 Live Preview 让调试极其方便
- 怎么开始:收集每个手势 500 张图片(包含 Background 类),在 Create ML app 中训练,集成 Vision 的
VNDetectHumanHandPoseRequest获取关键点输入模型
2. 双手协作的创意工具
- 做什么:左手控制画笔颜色(不同手势切换颜色),右手控制画笔粗细(手指张开程度)
- 为什么值得做:
chirality属性让双手独立控制成为可能,一只手的状态不影响另一只手的识别 - 怎么开始:分别训练两个 Hand Pose Classifier,一个识别左手手势,一个识别右手手势,用
chirality过滤各自的输入
3. 手势密码/身份验证
- 做什么:用户定义一套专属手势序列作为 App 的解锁密码
- 为什么值得做:Hand Action Classification 可以识别连续动作序列,比静态手势更安全(难以被偷拍复制)
- 怎么开始:用 Hand Action Classification 训练用户的自定义动作,每个动作录制 100 段 1.5 秒视频,在 App 中累积姿态队列进行匹配
4. 无声指令系统
- 做什么:在图书馆、会议室等需要保持安静的场景,用手势控制音乐播放、幻灯片翻页
- 为什么值得做:Vision 的手部检测在普通室内光照下表现稳定,不需要额外的硬件
- 怎么开始:定义一组简单手势(下一首、暂停、音量加减),训练 Hand Pose Classifier,结合
VNDetectHumanHandPoseRequest实时检测
关联 Session
- Build dynamic iOS apps with the Create ML framework — Create ML on-device 训练,可用于在 App 内动态训练手势模型
- Detect people, faces, and poses using Vision — Vision 手部姿态检测 API,是手势分类的输入来源
- Tune your Core ML models — 优化手势分类模型的执行性能
- Detect Body and Hand Pose with Vision — WWDC20 的手部姿态检测基础,了解 VNDetectHumanHandPoseRequest
评论
GitHub Issues · utterances